Skip to content

Conversation

@RinkalBhojani
Copy link
Contributor

@RinkalBhojani RinkalBhojani commented Nov 17, 2025

What?

Below features are added for experimental purpose. Functionalities are working but may have some issues

  • OID4VC
  • OID4VP
  • X509

Summary by CodeRabbit

  • New Features

    • OpenID4VC issuance: issuer & template management, credential-offer creation (D2A), issuance webhooks, DTOs and APIs.
    • OpenID4VP verification: verifier management, verification sessions, presentation responses and webhooks.
    • X.509 support: certificate create/import, activate/deactivate, listing and retrieval endpoints.
    • Agent & gateway: expanded agent capabilities and new DTOs/validation; conditional loading of OIDC/X.509 controllers.
  • Chores

    • Added service Docker images and CI matrix entries.
    • Added environment variables and HIDE_EXPERIMENTAL_OIDC_CONTROLLERS flag.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Adds OIDC4VC issuance and verification plus X.509 services across the monorepo: new API Gateway modules/controllers/services/DTOs, new microservices (issuance, verification, x509) with NATS message patterns and repositories, Dockerfiles, CI matrix entries, env seeds and feature flag, Prisma migrations/seed data, many DTOs/interfaces, and a broad NATS-call refactor to direct natsClient usage.

Changes

Cohort / File(s) Change Summary
Env & CI
'.env.demo', '.env.sample', '.github/workflows/continuous-delivery.yml'
Added X509_NKEY_SEED, OIDC4VC_ISSUANCE_NKEY_SEED, OIDC4VC_VERIFICATION_NKEY_SEED and feature flag HIDE_EXPERIMENTAL_OIDC_CONTROLLERS=true; added x509, oid4vc-issuance, oid4vc-verification to CI workflow matrix.
Dockerfiles
Dockerfiles/*
e.g. Dockerfiles/Dockerfile.x509, Dockerfiles/Dockerfile.oid4vc-issuance, Dockerfiles/Dockerfile.oid4vc-verification
Added multi-stage Dockerfiles for x509, oid4vc-issuance and oid4vc-verification (build stage with pnpm/prisma generate/build; runtime stage copying dist/libs and running prisma migrate/generate then start).
API Gateway — OIDC Issuance
apps/api-gateway/src/oid4vc-issuance/..., apps/api-gateway/src/app.module.ts, apps/api-gateway/src/main.ts
New DTOs/controllers/services/modules, NATS client wiring using ISSUANCE service constant and API_GATEWAY_NKEY_SEED, conditional registration via shouldLoadOidcModules() tied to HIDE_EXPERIMENTAL_OIDC_CONTROLLERS.
API Gateway — OIDC Verification
apps/api-gateway/src/oid4vc-verification/...
New DTOs/controllers/services/modules and NATS client registration for OID4VP verification flows.
API Gateway — X.509
apps/api-gateway/src/x509/...
New X.509 DTOs, controller (CRUD + import), service delegating via NATS, and module wiring.
Agent Service
apps/agent-service/src/*
Added many MessagePattern handlers for OIDC/issuer/credential-offer, X.509, and verifier flows; injected Logger; registered NatsInterceptor as APP_INTERCEPTOR; expanded controller/service public method signatures.
Oid4vc-Issuance Microservice
apps/oid4vc-issuance/...
New microservice bootstrap, controller handlers, service, repository, helpers (credential-sessions.builder, issuer.metadata), interfaces, constants, Prisma migrations, tests and tsconfig.
Oid4vc-Verification Microservice
apps/oid4vc-verification/...
New microservice bootstrap, controller handlers, service, repository, DTOs/interfaces for verifier/session handling, tests and tsconfig.
X509 Microservice
apps/x509/...
New microservice bootstrap, controller (message-pattern handlers), service, repository, DTOs/interfaces, tsconfig and NATS wiring.
NATS / Integration refactor
multiple services, e.g. apps/connection/**, apps/verification/**, apps/issuance/**, apps/cloud-wallet/**, apps/webhook/**
Replaced legacy natsCall wrappers with direct natsClient.send usage; updated return types to raw payloads; standardized error extraction and RpcException wrapping; removed some unused injected dependencies.
Common libs & enums
libs/common/src/*, libs/enum/src/enum.ts, libs/common/src/cast.helper.ts, libs/common/src/date-only.ts
Added OIDC/X.509 URL constants and microservice names, new enums and interfaces, response messages, buildUrlWithQuery, DateOnly, shouldLoadOidcModules() helper; changed getAgentUrl to synchronous and expanded endpoint mappings.
Prisma / Migrations / Seed
libs/prisma-service/prisma/migrations/*, libs/prisma-service/prisma/data/credebl-master-table.json
Added migrations for credential_templates and oidc_issuer schema changes (issuer relations, signer key type, batch size) and added seed JSON data.
Helpers & utilities
assorted apps/*, libs/*
Async file-operation helper refactor, cast helper utility, logging/context interceptor improvements, many DTO/interface additions and refinements, removal of unused constructor DI, minor formatting and test rewrites.

Sequence Diagram(s)

sequenceDiagram
    participant Client as API Client
    participant API as API Gateway
    participant IssSvc as Oid4vcIssuanceService
    participant NATS as NATS
    participant Agent as Agent
    participant DB as Database

    Client->>API: POST /orgs/:orgId/openid4vc/issuer
    API->>IssSvc: validate & call oidcIssuerCreate(orgId, dto, user)
    IssSvc->>NATS: send(create-oid4vc-issuer, payload)
    NATS->>Agent: forward to agent microservice
    Agent-->>NATS: reply (issuer created)
    NATS-->>IssSvc: response
    IssSvc->>DB: persist issuer metadata
    IssSvc-->>API: return created issuer
    API-->>Client: 201 Created + issuer
Loading
sequenceDiagram
    participant Client as API Client
    participant API as API Gateway
    participant X509Svc as X509Service
    participant Repo as X509Repository
    participant NATS as NATS
    participant Agent as Agent

    Client->>API: POST /orgs/:orgId/x509
    API->>X509Svc: createX509(orgId, dto, user)
    X509Svc->>Repo: hasDateCollision(options)
    alt no collision
      X509Svc->>NATS: send('create-x509-certificate', payload)
      NATS->>Agent: request certificate generation
      Agent-->>NATS: certificate data
      NATS-->>X509Svc: cert data
      X509Svc->>Repo: create(record)
      X509Svc-->>API: 201 + certificate
      API-->>Client: 201 + certificate
    else collision
      X509Svc-->>API: 409 Conflict
      API-->>Client: 409 Conflict
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Focus areas for review:
    • apps/oid4vc-issuance/** (service business logic, credential-sessions.builder, issuer.metadata, rollback/error handling).
    • apps/x509/** (certificate collision logic, import/activation flows, agent NATS interactions and security of key handling).
    • Prisma migrations and seed data (FKs, non-null additions, migration safety and data migration strategy).
    • NATS refactor across services (subject names, changed return shapes, consistent error handling and RpcException mapping).
    • libs/common changes (getAgentUrl sync change, new constants/enums, shouldLoadOidcModules behavior and feature-flag impact on module load).

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • RinkalBhojani
  • GHkrishna
  • ankita-p17

Poem

🐰 I hopped through code with whiskers bright,
Issuers, verifiers, and certs took flight.
NATS hummed softly while DTOs found home,
Dockerfiles stacked and CI seeds roamed.
Carrot and logs — the rabbit's delight! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding three experimental features (OID4VC, OID4VP, X509) to the platform, which aligns well with the extensive changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/oidc-main-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 57

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/webhook/src/webhook.service.ts (2)

28-54: Fix return type inconsistency in registerWebhook.

The method signature declares Promise<ICreateWebhookUrl> but line 47 returns webhookUrl.webhookUrl (a string property). This creates a type mismatch between the declared return type and the actual return value.

Apply this diff to return the complete object:

-        return webhookUrl.webhookUrl;
+        return webhookUrl;

Or update the method signature if returning just the URL string is intentional:

-async registerWebhook(registerWebhookDto: IWebhookDto): Promise<ICreateWebhookUrl> {
+async registerWebhook(registerWebhookDto: IWebhookDto): Promise<string> {

56-70: Fix return type inconsistency in getWebhookUrl.

Similar to registerWebhook, the method signature declares Promise<IGetWebhookUrl> but line 65 returns webhookUrlInfo.webhookUrl (a string property), creating a type mismatch.

Apply this diff to return the complete object:

-      return webhookUrlInfo.webhookUrl;
+      return webhookUrlInfo;

Or update the method signature if returning just the URL string is intentional:

-async getWebhookUrl(getWebhook: IWebhookUrl): Promise<IGetWebhookUrl> {
+async getWebhookUrl(getWebhook: IWebhookUrl): Promise<string> {
🧹 Nitpick comments (40)
apps/issuance/src/issuance.service.ts (1)

1519-1521: LGTM! Type annotation improves type safety.

The explicit type annotation for the ms parameter is a good improvement for type safety. The method implementation is correct.

As an optional refinement, consider changing the return type from Promise<unknown> to Promise<void> for consistency with the local delay function defined at line 1533, since this method doesn't resolve with any meaningful value:

-async delay(ms: number): Promise<unknown> {
+async delay(ms: number): Promise<void> {
   return new Promise((resolve) => setTimeout(resolve, ms));
 }
apps/api-gateway/src/connection/connection.service.ts (2)

39-49: Apply consistent error handling pattern.

The sendBasicMessage method still uses the old error handling pattern without the safe fallback. For consistency with the updated sendQuestion method and to prevent potential loss of error context, apply the same pattern here.

Apply this diff:

     } catch (error) {
-      throw new RpcException(error.response);
+      throw new RpcException(error?.response ?? error);
     }

56-65: Apply consistent error handling pattern.

The getUrl method also uses the old error handling pattern. Apply the same safe fallback pattern for consistency across the file.

Apply this diff:

     } catch (error) {
-      throw new RpcException(error.response);
+      throw new RpcException(error?.response ?? error);
     }
apps/api-gateway/src/connection/enums/connections.enum.ts (1)

1-4: Consider consolidating duplicate enum definitions.

The HandshakeProtocol enum is duplicated identically in both apps/api-gateway/src/connection/enums/connections.enum.ts and apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts. Consolidating into a shared location (e.g., a common enums directory) would reduce maintenance overhead and prevent potential divergence.

apps/ledger/src/schema/schema.service.ts (1)

405-405: Consider addressing these TODOs or tracking them in an issue.

The TODO comments correctly identify that the map((response) => ({ response })) pattern adds unnecessary nesting. The string response from NATS is wrapped in an object, only to be unwrapped by callers accessing .response. This could be simplified by either:

  • Returning the response directly and updating return types
  • Removing the map operator entirely if no transformation is needed

However, these TODOs appear unrelated to the PR's main objectives (OID4VC/X509 features).

Would you like me to:

  1. Generate a refactored implementation that removes this nested mapping across all three methods?
  2. Open a separate issue to track this technical debt for future cleanup?

Also applies to: 433-433, 558-558

apps/utility/src/utilities.repository.ts (1)

13-28: Add type annotation for the payload parameter.

The payload parameter lacks a type annotation, reducing type safety and IDE support. Consider defining an interface or type for the expected shape.

Apply this diff to add a type annotation:

+interface ShorteningUrlPayload {
+  referenceId: string;
+  invitationPayload: unknown;
+}
+
-  async saveShorteningUrl(payload): Promise<object> {
+  async saveShorteningUrl(payload: ShorteningUrlPayload): Promise<object> {
apps/webhook/src/webhook.service.ts (1)

72-90: Consider logging error details in the catch block.

The catch block at line 87-89 doesn't log the actual error details before throwing the exception, which could make debugging webhook failures more difficult.

Apply this diff to improve error visibility:

     } catch (err) {
+      this.logger.error(`Error in webhookFunc: ${JSON.stringify(err)}`);
       throw new InternalServerErrorException(ResponseMessages.webhook.error.webhookResponse);
     }
libs/context/src/contextModule.ts (2)

18-19: Remove redundant camelCase header check.

HTTP header names are case-insensitive, but Express (used by NestJS) normalizes all header names to lowercase. The check for req.headers['contextId'] (camelCase) will never match because Express converts it to 'contextid'.

Simplify to use only lowercase header keys:

-          const contextIdHeader = req.headers['contextid'] ?? req.headers['context-id'] ?? req.headers['contextId'];
+          const contextIdHeader = req.headers['contextid'] ?? req.headers['context-id'];
           const correlationIdHeader = req.headers['x-correlation-id'];

24-31: Use structured logging instead of console.log.

The direct console.log statements bypass the application's logging infrastructure. Using a proper logger would provide structured logging, appropriate log levels, and better integration with observability tools—especially important for this experimental feature.

Consider using the application's logger service:

import { Logger } from '@nestjs/common';

// Inside the module or as a constant
const logger = new Logger('ContextModule');

// In the idGenerator function
if (resolvedContextId) {
  logger.log(`ContextId received in request headers: ${resolvedContextId}`);
} else {
  resolvedContextId = v4();
  logger.log(`ContextId not received in request headers, generated: ${resolvedContextId}`);
}

Alternatively, if this is intentional for debugging the experimental feature, consider using logger.debug() so it can be toggled via log level configuration.

libs/context/src/contextInterceptorModule.ts (2)

19-19: Move Logger instantiation to module scope.

Creating a new Logger instance on every request adds unnecessary overhead. Since idGenerator is invoked for each incoming request, this could degrade performance under load.

Apply this diff to move the Logger to module scope:

+const logger = new Logger('ContextInterceptorModule');
+
 @Global()
 @Module({
   imports: [
     ClsModule.forRoot({
       global: true,
       interceptor: {
         mount: true,
         generateId: true,
         idGenerator: (context: ExecutionContext) => {
           try {
-            const logger = new Logger('ContextInterceptorModule');
             const rpcContext = context.switchToRpc().getContext();

20-22: Inconsistent defensive patterns for header handling - consider normalizing approach.

The review comment's observation about type inconsistency is confirmed. The codebase uses two different defensive patterns:

  1. logging.interceptor.ts (line 27-30): Guard-based approach using isNullUndefinedOrEmpty() check before calling .get()
  2. contextInterceptorModule.ts (line 21-22): Defensive fallback + optional chaining approach with ?? {} and headers.get?.()

Both patterns handle the case where getHeaders() can return null/undefined, but the inconsistency suggests either:

  • getHeaders() may return null/undefined in RPC contexts
  • Or there's uncertainty about the return type across the codebase

Both implementations work correctly, but for improved consistency and type safety, consider normalizing to one pattern across both files. The logging.interceptor.ts pattern is slightly more explicit about the guard condition.

apps/cloud-wallet/src/cloud-wallet.service.ts (1)

43-47: Remove unused dependency registrations from CloudWalletModule.

The constructor cleanup is correct—cloudWalletServiceProxy and cacheService are not referenced in any service methods. However, the module configuration still registers unused dependencies: NATS_CLIENT and CacheModule.register() are imported and registered but no longer injected into CloudWalletService. Remove these unused registrations from apps/cloud-wallet/src/cloud-wallet.module.ts to avoid instantiating unused providers on app boot.

apps/api-gateway/src/agent-service/dto/create-schema.dto.ts (1)

29-31: Consider validating orgId as a string, not just non-empty

orgId currently only has @IsNotEmpty, unlike the other string fields which also use @IsString. For consistency and stricter runtime validation, it’s better to enforce the type as well.

Suggested change:

   @ApiProperty()
-  @IsNotEmpty({ message: 'please provide orgId' })
-  orgId: string;
+  @IsString({ message: 'orgId must be a string' })
+  @IsNotEmpty({ message: 'please provide orgId' })
+  orgId: string;

If you want to confirm that this pattern matches current best practices for class-validator, you can reuse the same documentation lookup as in the previous comment.

libs/prisma-service/prisma/migrations/20250904181430_added_fields_in_oidc_issuer/migration.sql (1)

4-6: Consider if array type is necessary for accessTokenSignerKeyType.

The AccessTokenSignerKeyType ENUM currently has only one value ('ed25519'), and the column is defined as an array. If multiple signer key types per issuer are not immediately required, a scalar type might be simpler.

libs/prisma-service/prisma/migrations/20250822104325_oidc_issuer_templates/migration.sql (1)

4-17: Consider using JSONB instead of JSON for better performance.

The attributes and appearance columns are defined as JSON type. PostgreSQL's JSONB type offers better performance for querying and indexing. If later migrations update these to JSONB, this comment can be ignored.

apps/issuance/constant/issuance.ts (1)

4-4: Verify if empty credentialConfigurationsSupported is intentional.

The credentialConfigurationsSupported constant is an empty object. This may be a placeholder for future implementation or work in progress.

Do you want me to track this as a TODO or is this intentionally empty for the experimental feature?

apps/api-gateway/src/helper-files/file-operation.helper.ts (1)

16-26: Async wrappers for getFile / deleteFile look fine

getFile and deleteFile correctly wrap fs.readFile / fs.unlink with promisify and expose promise-based APIs. No functional concerns here. You could optionally hoist the promisified functions to module scope to avoid re-creating them on each call, but that’s not critical.

apps/connection/src/connection.controller.ts (1)

49-52: Confirm whether user / userId should be forwarded to the service

In both handlers:

  • Line 49: getConnectionListFromAgent(payload: GetAllConnections) – payload includes user, but only orgId and connectionSearchCriteria are forwarded.
  • Line 67: getConnectionRecordsByOrgId(payload: { orgId: string; userId: string })userId is accepted but never used.

If authorization/auditing is expected at the service layer, these fields might need to be forwarded; otherwise they can be dropped from the payload types to avoid confusion.

Also applies to: 67-70

apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces.ts (1)

1-47: Template interfaces are coherent; consider type-only Prisma import

The SdJwt/Mdoc template and credential attribute structures look consistent and expressive for OID4VC template handling, including recursive children and appearance as Prisma.JsonValue.

Since Prisma and SignerOption are used purely as types here, you could switch to a type-only import to avoid unnecessary runtime dependency wiring:

-import { Prisma, SignerOption } from '@prisma/client';
+import type { Prisma, SignerOption } from '@prisma/client';

This is a minor build/runtime cleanliness improvement.

apps/oid4vc-issuance/interfaces/oid4vc-wh-interfaces.ts (1)

1-48: Align CredentialPayload naming and verify credDefid spelling

There are a couple of potential confusion points here:

  • This file exports CredentialPayload with specific fields (orgId, schemaId?, credDefid?, etc.), but apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts already defines another exported CredentialPayload with a different shape. Sharing the same name for different concepts in the same bounded context makes mis-imports very likely.

  • The property credDefid (line 24) looks like it may be intended as credDefId. If other parts of the codebase use credDefId, this mismatch will silently produce undefined at runtime.

Consider either:

  • Renaming one of the interfaces (e.g., IssuerSessionCredentialPayload vs WebhookCredentialPayload), or
  • Unifying them if they are meant to represent the same structure.

And please verify the credDefid key name across usages.

libs/common/src/response-messages/index.ts (1)

506-622: New OIDC/OID4VP/x509 message blocks look good; consider minor cleanup

The added oidcIssuer, oidcTemplate, oidcIssuerSession, oid4vp, oid4vpSession, and x509 message groups provide good coverage of the new flows’ success and error cases and align with the rest of the catalog.

A couple of small nits you may want to address:

  • oidcTemplate.success.deleteTemplate is a literal string containing ${templateId}. Unless you explicitly .replace or interpolate this at call sites, logs/clients will see the placeholder verbatim. You might prefer a static message (e.g., '[createTemplate] compensating delete succeeded') and log the templateId separately, or move to a helper that formats the message with a parameter.

  • x509.error.errorActivation currently reads 'Failed to activate x509 certificate..' (double period). Dropping one dot would improve polish.

These are cosmetic improvements and don’t block functionality.

.env.sample (1)

70-71: Address formatting issues for consistency.

The static analysis tool flags ordering and formatting inconsistencies. While the comment is helpful, consider moving this variable to maintain alphabetical grouping with other configuration flags.

-POSTGRES_DATABASE= // Please provide your DB name
+POSTGRES_DATABASE= // Please provide your DB name
+
 # Default is true too, set to 'false', to view OIDC controllers in openAPI docs
 HIDE_EXPERIMENTAL_OIDC_CONTROLLERS=true
-SENDGRID_API_KEY=xxxxxxxxxxxxxx // Please provide your sendgrid API key
+
+SENDGRID_API_KEY=xxxxxxxxxxxxxx // Please provide your sendgrid API key
.env.demo (1)

147-148: Consider ordering for consistency.

Static analysis suggests moving HIDE_EXPERIMENTAL_OIDC_CONTROLLERS earlier in the file for better organization. While this is a demo file and the flag is functional, maintaining consistent ordering with .env.sample would improve maintainability.

apps/api-gateway/src/oid4vc-verification/oid4vc-verification.module.ts (1)

26-26: Clarify module naming convention.

The module is named Oid4vpModule but the file is oid4vc-verification.module.ts and the controller/service use Oid4vcVerification prefix. Consider renaming to Oid4vcVerificationModule for consistency across the codebase.

Apply this diff to align the naming:

-export class Oid4vpModule {}
+export class Oid4vcVerificationModule {}
libs/prisma-service/prisma/data/credebl-master-table.json (1)

3-20: Treat this file strictly as template data; avoid real secrets and env‑specific IDs.

Values like sgApiKey, password, externalIp, and apiEndpoint are clearly placeholders, but the presence of a concrete supabaseUserId suggests this file could accidentally be edited with real tenant‑specific data. Please ensure:

  • No real API keys, passwords, or tenant identifiers are ever committed here.
  • If this is used as a seed template, document that deployers must override these values via environment‑specific seed files or env variables rather than editing this one in‑repo.

Also applies to: 75-138, 170-177

apps/oid4vc-issuance/src/oid4vc-issuance.module.ts (1)

1-36: Module wiring looks consistent; consider adding MICRO_SERVICE_NAME for observability consistency.

The NATS setup and imports align with existing patterns (e.g., IssuanceModule). To keep logging/metrics consistent across services, you may want to also provide MICRO_SERVICE_NAME here (similar to Issuance/Agent modules) so downstream code can tag logs with a stable service identifier.

Dockerfiles/Dockerfile.oid4vc-verification (1)

1-45: Image is functional; consider separating migrations from app startup.

The multi‑stage build looks fine and reuses the pattern used elsewhere. Two things to consider:

  • CMD runs prisma migrate deploy and prisma generate on every container start. In multi‑replica setups this can cause redundant or conflicting migration runs. It’s usually safer to run migrations in a dedicated job/init container and keep the app container focused on node dist/.../main.js.
  • You install dependencies with pnpm but build via npm run build. That’s valid (since npm run just invokes the script and uses the existing node_modules), but it’s worth double‑checking that your root scripts don’t assume a lockfile or a different package manager at runtime.
apps/oid4vc-verification/src/oid4vc-verification.module.ts (1)

1-52: Avoid duplicating PrismaService in both module imports and providers.

You import PrismaServiceModule and also list PrismaService directly in providers. Nest will tolerate this, but it’s redundant and can be confusing.

I’d suggest either:

  • Keep PrismaServiceModule in imports and drop PrismaService from providers, or
  • Remove PrismaServiceModule and only provide PrismaService directly (matching your pattern in other services).

The rest of the module wiring (NATS client, MICRO_SERVICE_NAME, common infra modules) looks consistent.

apps/oid4vc-issuance/constant/issuance.ts (1)

4-4: Clarify the purpose of the empty object.

credentialConfigurationsSupported is exported as an empty object. If this is a placeholder for future configuration, consider adding a TODO comment or type annotation to document its intended structure.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts (1)

32-38: Add @IsString decorator to optional string fields.

The optional fields type and orgId are missing the @IsString() decorator. While @IsOptional() allows these fields to be omitted, when they are provided, they should still be validated as strings for consistency with the required fields.

Apply this diff:

   @ApiPropertyOptional()
   @IsOptional()
+  @IsString()
   type: string;
 
   @ApiPropertyOptional()
   @IsOptional()
+  @IsString()
   orgId: string;
apps/api-gateway/src/verification/dto/webhook-proof.dto.ts (1)

11-13: Consider a more specific type for metadata.

The metadata property is typed as object, which provides minimal type safety. If the structure of metadata is known, consider defining an interface or using a more specific type like Record<string, unknown>.

apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier.dto.ts (1)

6-20: DTOs look consistent; consider stricter validation for logo_uri.

The DTOs line up cleanly with the shared ClientMetadata / CreateVerifier contracts, and nested validation is wired correctly. If you want to tighten validation a bit, you could add @IsUrl() on logo_uri (and keep @IsString() if desired) to fail fast on invalid logo URLs, but that’s optional.

Also applies to: 22-42, 44-44

apps/x509/src/interfaces/x509.interface.ts (1)

4-13: Align status type with x5cRecordStatus for consistency.

CreateX509CertificateEntity.status is typed as string while you already use x5cRecordStatus elsewhere and import it here. Using the enum here as well would improve type-safety and keep the X.509 model consistent:

 export interface CreateX509CertificateEntity {
   orgId: string; // We'll accept orgId and find orgAgent internally
   keyType: x5cKeyType;
-  status: string;
+  status: x5cRecordStatus;
   validFrom: Date;
   expiry: Date;
   certificateBase64: string;
   createdBy: string;
   lastChangedBy: string;
 }
apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (3)

69-104: Align documented vs actual HTTP status codes for create/update verifier.

Both verifier create/update endpoints declare @ApiResponse({ status: HttpStatus.OK, ... }) but actually return HttpStatus.CREATED and set statusCode: HttpStatus.CREATED in the payload. This mismatch will confuse API consumers and tooling that rely on the OpenAPI spec.

Consider either:

  • changing the decorators to HttpStatus.CREATED, or
  • returning HttpStatus.OK (200) and statusCode: HttpStatus.OK, especially for the update endpoint where 200 is more conventional.

Also applies to: 114-155


208-249: Use an appropriate success message for delete verifier.

The delete endpoint returns ResponseMessages.oid4vp.success.fetch even though it performs a delete. That’s likely a copy‑paste from the GET handler and could be misleading for clients or logs.

If you have a dedicated delete message key (e.g. success.delete), it’d be clearer to use that here; otherwise, at least consider a more delete‑specific message string.


421-459: Webhook handler is consistent with issuance flow; consider optional sanitization.

The webhook endpoint mirrors the OID4VC issuance controller pattern (type tagging, orgId fallback on contextCorrelationId === 'default', service delegation, and optional org webhook repost). From a behavior standpoint that’s consistent.

If you expect high-sensitivity data in oid4vpPresentationWhDto, you may later want a sanitization step before logging or reposting to external webhooks, but that’s more of a hardening pass than a blocker right now.

libs/common/src/interfaces/oid4vp-verification.ts (1)

1-26: Interfaces are coherent; you can reuse ClientMetadata in VerifierRecord.

The interfaces nicely mirror the DTOs and service expectations. To avoid duplicating the shape of client metadata, you could slightly tighten VerifierRecord:

 export interface VerifierRecord {
   _tags: Record<string, unknown>;
   metadata: Record<string, unknown>;
   id: string;
   createdAt: string; // ISO timestamp
   verifierId: string;
-  clientMetadata: {
-    client_name: string;
-    logo_uri: string;
-  };
+  clientMetadata: ClientMetadata;
   updatedAt: string; // ISO timestamp
 }

Not required, but it keeps types DRY and easier to evolve.

apps/oid4vc-verification/src/oid4vc-verification.controller.ts (1)

1-105: Microservice handlers look clean and aligned with the gateway/service contracts.

The message handlers are thin, log useful context, and delegate cleanly to Oid4vpVerificationService with correctly typed payloads. The overall structure matches the API-gateway controller semantics, which should keep the OID4VP flow easy to trace end‑to‑end. You can revisit the TODO about renaming the 'oid4vp-verification-session-create' command later, but nothing blocking here.

apps/agent-service/src/agent-service.service.ts (1)

1426-1556: Consider extracting a common HTTP call helper.

All OIDC methods (oidcIssuerCreate, deleteOidcIssuer, oidcGetIssuerById, etc.) follow an identical pattern:

  1. Get organization API key
  2. Make HTTP call (POST/GET/PUT/DELETE)
  3. Return response
  4. Log and rethrow errors

This could be refactored into a generic helper method to reduce the ~130 lines of duplication.

Example helper:

private async agentHttpCall<T>(
  method: 'POST' | 'GET' | 'PUT' | 'DELETE',
  url: string,
  orgId: string,
  operationName: string,
  payload?: unknown
): Promise<T> {
  try {
    const getApiKey = await this.getOrgAgentApiKey(orgId);
    const httpMethod = {
      POST: this.commonService.httpPost.bind(this.commonService),
      GET: this.commonService.httpGet.bind(this.commonService),
      PUT: this.commonService.httpPut.bind(this.commonService),
      DELETE: this.commonService.httpDelete.bind(this.commonService)
    }[method];
    
    const response = await httpMethod(url, payload, {
      headers: { authorization: getApiKey }
    });
    return response;
  } catch (error) {
    this.logger.error(`Error in ${operationName} in agent service : ${JSON.stringify(error)}`);
    throw error;
  }
}
apps/api-gateway/src/x509/dtos/x509.dto.ts (1)

207-207: Remove commented code.

Lines 207 and 295-296 contain commented-out code that should either be removed (if not needed) or uncommented (if needed). Leaving commented code in production reduces maintainability.

If not needed, remove:

  • Line 207: @ApiExtraModels(X509CertificateIssuerAndSubjectOptionsDto)
  • Lines 295-296: stateOrProvinceName and organizationalUnit properties

If needed, uncomment and ensure proper validation decorators are applied.

Also applies to: 295-296

Comment on lines 2 to 3
/* eslint-disable @typescript-eslint/explicit-function-return-type */
// TODO: Remove this
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Remove ESLint disable and TODO before merge; technical debt.

The ESLint disable rule for explicit-function-return-type and the associated TODO comment should not be committed. All methods in this service already have explicit return type annotations (e.g., lines 42–45, 61–63, 123–127, etc.). This disable rule is unnecessary and masks code quality checks.

Run the following script to verify all methods have explicit return types and confirm the disable can be safely removed:

Expected result: All method definitions should show explicit return type annotations. If any methods lack return types, they must be added before removing the ESLint disable.


🏁 Script executed:

#!/bin/bash
# Verify that all public/exported methods have explicit return types

ast-grep --pattern $'export class IssuanceService extends BaseService {
  $$$
  $_($$$): $return_type {
    $$$
  }
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for method definitions in IssuanceService that may lack explicit return types
# Look for lines with method signatures to analyze pattern

rg -nP -A1 '^\s*(async\s+)?\w+\s*\(' apps/api-gateway/src/issuance/issuance.service.ts | head -50

Length of output: 1849


🏁 Script executed:

#!/bin/bash
# More targeted: search for method signatures and check return type presence
# Pattern: find lines with parentheses followed by { without : (colon for return type)

rg -nP '^\s+(async\s+)?\w+\s*\([^)]*\)\s*\{' apps/api-gateway/src/issuance/issuance.service.ts

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Show the actual method definitions in the file with context
cat apps/api-gateway/src/issuance/issuance.service.ts | head -100

Length of output: 2873


🏁 Script executed:

#!/bin/bash
# Find method definitions (public methods in class) without explicit return types
# Look for pattern: ) { without preceding : 

rg -nP '^\s+(async\s+)?[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)\s*:\s*' apps/api-gateway/src/issuance/issuance.service.ts | wc -l

Length of output: 2


🏁 Script executed:

#!/bin/bash
# Find the entire file to see all methods
wc -l apps/api-gateway/src/issuance/issuance.service.ts

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Extract all method signatures (lines with method definitions) to review comprehensively
# Search for methods in the class - look for ) { or ): ReturnType {
rg -nP '^\s+(async\s+)?[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)\s*(?::\s*\S+\s*)?\{' apps/api-gateway/src/issuance/issuance.service.ts

Length of output: 1174


🏁 Script executed:

#!/bin/bash
# Find all method definitions without explicit return types
# Methods without return types would have ) { without preceding :
# Look for method patterns: ) { where there's no : before the {

rg -nP '^\s+(async\s+)?[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)\s*\{' apps/api-gateway/src/issuance/issuance.service.ts

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Show methods around lines 42, 123, 132, 143, 153, 200 to verify their signatures
sed -n '42,48p; 123,128p; 132,140p; 143,151p; 153,162p; 200,210p' apps/api-gateway/src/issuance/issuance.service.ts

Length of output: 1732


Remove ESLint disable and TODO before merge.

All methods in this service have explicit return type annotations. The @typescript-eslint/explicit-function-return-type disable rule and TODO comment should not be committed—they represent unnecessary technical debt. Lines 2–3 must be removed.

🤖 Prompt for AI Agents
In apps/api-gateway/src/issuance/issuance.service.ts around lines 2 to 3, remove
the file-level ESLint disable comment "// eslint-disable
@typescript-eslint/explicit-function-return-type" and the "// TODO: Remove this"
comment; delete those two lines and ensure no other file-level disables remain,
then run the linter to confirm no rule violations and commit the change.

Comment on lines +1 to +2
-- Drop the table if it exists (safe for dev)
DROP TABLE IF EXISTS "public"."credential_templates";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify that DROP TABLE is safe for all environments.

The migration includes a DROP TABLE IF EXISTS statement, which will delete all data in the credential_templates table if it exists. While the comment states "safe for dev", migrations typically run in all environments unless explicitly controlled.

Run the following script to check if this migration has already been applied in production:


🏁 Script executed:

#!/bin/bash
# Description: Check migration history and verify if safe to drop table

# Check if migration is already applied
echo "Checking migration timestamp: 20250822104325"

# Look for any references to credential_templates table being used
rg -n "credential_templates" --type sql -C 3

Length of output: 7287


To complete verification, I need to confirm whether this migration file is new to the repository or already exists:

Remove the misleading comment about development-only safety.

The comment "safe for dev" incorrectly implies this migration applies only in development. Prisma migrations apply in production and testing environments the same way they do in development. However, since the migration is already applied (as evidenced by later migrations successfully referencing the credential_templates table), it won't re-run due to Prisma's migration tracking.

The DROP TABLE IF EXISTS pattern is appropriate here for initial schema creation. However, clarify the comment to avoid confusion:

-- Initial table creation (dropping any stale table if it exists)
DROP TABLE IF EXISTS "public"."credential_templates";
🤖 Prompt for AI Agents
In
libs/prisma-service/prisma/migrations/20250822104325_oidc_issuer_templates/migration.sql
around lines 1 to 2, the comment "safe for dev" is misleading because Prisma
migrations run in all environments; replace that comment with a neutral,
accurate one such as indicating this is the initial table creation and that the
DROP TABLE IF EXISTS is used to remove any stale table before creation (e.g.,
"-- Initial table creation (dropping any stale table if it exists)"), leaving
the DROP statement unchanged.

@RinkalBhojani
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 44

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/webhook/src/webhook.service.ts (2)

28-54: Return type mismatch: returning string instead of interface object.

The method signature declares the return type as ICreateWebhookUrl, but line 47 returns webhookUrl.webhookUrl, which is just a string. This creates a type mismatch that could cause issues with NATS message serialization or consuming code that expects an object.

Either:

  1. Return the full object: return webhookUrl; to match the interface type, or
  2. Update the return type to Promise<string> if only the URL string is needed

The same issue exists in the API gateway service snippet, so verify consistency across both services.

Apply this diff if the full object should be returned:

-        return webhookUrl.webhookUrl;
+        return webhookUrl;

Or update the return type if only the string is needed:

-  async registerWebhook(registerWebhookDto: IWebhookDto): Promise<ICreateWebhookUrl> {
+  async registerWebhook(registerWebhookDto: IWebhookDto): Promise<string> {

56-70: Return type mismatch: returning string instead of interface object.

Similar to registerWebhook, this method declares the return type as IGetWebhookUrl but returns only the string webhookUrlInfo.webhookUrl on line 65. This creates the same type mismatch issue.

Apply this diff if the full object should be returned:

-      return webhookUrlInfo.webhookUrl;
+      return webhookUrlInfo;

Or update the return type if only the string is needed:

-  async getWebhookUrl(getWebhook: IWebhookUrl): Promise<IGetWebhookUrl> {
+  async getWebhookUrl(getWebhook: IWebhookUrl): Promise<string> {
♻️ Duplicate comments (31)
apps/issuance/src/issuance.controller.ts (1)

1-2: The blanket ESLint disable remains unresolved.

This issue was already flagged in a previous review. The blanket ESLint disable continues to mask potential code quality issues across the entire controller, and the TODO comment provides no actionable timeline or specifics for resolution.

apps/connection/src/connection.service.ts (2)

64-64: Replace Promise<unknown> with a specific return type.

As previously noted, the return type Promise<unknown> provides no type safety. Properties accessed at lines 534, 540, and 557 confirm this should return a typed interface.


576-577: Replace Promise<any> with a specific interface.

As previously noted, the any return type weakens type safety. Define a specific interface based on the properties accessed at lines 534, 540, and 557.

apps/utility/src/utilities.repository.ts (1)

30-43: Handle findUnique null case and add referenceId type.

This method still has the two issues previously flagged in earlier review comments:

  1. findUnique can return null, but:

    • The return type is Promise<shortening_url>, suggesting a record is always present.
    • UtilitiesService.getShorteningUrl immediately dereferences properties on the result, which will throw at runtime when no record exists.
  2. referenceId is untyped, which weakens type-safety for a key lookup.

A safer contract is to keep the non-null return type and convert the “not found” case into a clear error, plus add the parameter type:

-  // eslint-disable-next-line camelcase
-  async getShorteningUrl(referenceId): Promise<shortening_url> {
+  // eslint-disable-next-line camelcase
+  async getShorteningUrl(referenceId: string): Promise<shortening_url> {
     try {
       const storeShorteningUrl = await this.prisma.shortening_url.findUnique({
         where: {
           referenceId
         }
       });
 
-      this.logger.log(`[getShorteningUrl] - shortening url details ${referenceId}`);
-      return storeShorteningUrl;
+      if (!storeShorteningUrl) {
+        this.logger.warn(`[getShorteningUrl] - no shortening url found for referenceId ${referenceId}`);
+        throw new Error(`Shortening URL not found for referenceId: ${referenceId}`);
+      }
+
+      this.logger.log(`[getShorteningUrl] - shortening url details ${referenceId}`);
+      return storeShorteningUrl;

This keeps the repository API aligned with how the service currently uses it and avoids null-dereference crashes.

libs/common/src/cast.helper.ts (1)

527-542: Critical logic error requires fix before merge.

This issue was previously identified but not yet resolved. The condition on Line 530 has a critical flaw:

if (!queryParams || (queryParams?.length >= 0))

Problem: For arrays, length >= 0 is always true, causing the function to return early and never process array inputs. Only objects without a length property will be processed.

Impact: The function will silently fail for any array-based query parameters.

Apply the previously suggested fix:

-  if (!queryParams || (queryParams?.length >= 0)) {
-    return baseUrl
+  if (!queryParams || Object.keys(queryParams).length === 0) {
+    return baseUrl;
   }

Additionally, address the ESLint violations:

  • Add missing semicolon on line 531
  • Fix yoda conditions (lines 536, 541)
  • Consider using Record<string, unknown> instead of any for better type safety

Based on past review comment.

apps/oid4vc-verification/src/oid4vc-verification.repository.ts (4)

42-57: Handle possible null from org_agents_type.findFirst before destructuring

findFirst can return null, but the code destructures immediately:

const { agent } = await this.prisma.org_agents_type.findFirst({ where: { id: orgAgentId } });

If no record exists, this will throw when trying to destructure null, and the error path won’t be a clear NotFoundException.

Consider the pattern suggested in the earlier review:

- const { agent } = await this.prisma.org_agents_type.findFirst({
-   where: {
-     id: orgAgentId
-   }
- });
-
- this.logger.debug(`[getOrgAgentType] Found type=${agent}`);
- return agent;
+ const result = await this.prisma.org_agents_type.findFirst({
+   where: {
+     id: orgAgentId
+   }
+ });
+
+ if (!result) {
+   this.logger.warn(`[getOrgAgentType] No agent type found for orgAgentId=${orgAgentId}`);
+   // Use a domain-appropriate error here if you have one
+   throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound);
+ }
+
+ this.logger.debug(`[getOrgAgentType] Found type=${result.agent}`);
+ return result.agent;

101-120: Fix updateOid4vpVerifier catch log to reference the correct method

The catch block currently logs:

this.logger.error(`[updateOid4vpVerifier] Error in createOid4vpVerifier: ${error.message}`);

This is misleading when debugging. It should reference updateOid4vpVerifier:

- this.logger.error(`[updateOid4vpVerifier] Error in createOid4vpVerifier: ${error.message}`);
+ this.logger.error(`[updateOid4vpVerifier] Error in updateOid4vpVerifier: ${error.message}`);

138-155: Consider handling optional verifierId and fix log message in getVerifiersByVerifierId

Two points here:

  1. The signature allows verifierId?: string but the where clause always includes id: verifierId. Passing undefined will either be rejected by Prisma or yield surprising behavior. If verifierId is truly optional, build the filter conditionally:
const result = await this.prisma.oid4vp_verifier.findMany({
-  where: {
-    id: verifierId,
-    orgAgent: {
-      orgId
-    }
-  }
+  where: {
+    orgAgent: { orgId },
+    ...(verifierId && { id: verifierId })
+  }
});
  1. The error log refers to the wrong method:
this.logger.error(`[getVerifiersByVerifierId] Error in getVerifiersByPublicVerifierId: ${error.message}`);

This should be getVerifiersByVerifierId to keep logs consistent.

- this.logger.error(`[getVerifiersByVerifierId] Error in getVerifiersByPublicVerifierId: ${error.message}`);
+ this.logger.error(`[getVerifiersByVerifierId] Error in getVerifiersByVerifierId: ${error.message}`);

157-193: Fix Prisma findUnique/delete usage and optional verifierId in verifier lookups

In both getVerifierById and deleteVerifierByVerifierId:

const result = await this.prisma.oid4vp_verifier.findUnique({
  where: {
    id: verifierId,
    orgAgent: { orgId }
  }
});

await this.prisma.oid4vp_verifier.delete({
  where: {
    id: verifierId,
    orgAgent: { orgId }
  }
});

Issues:

  • findUnique / delete where filters must match a unique constraint shape; combining id (already unique) with a relational filter is not supported by Prisma and will either fail type‑checking or at runtime. If you want both conditions, use findFirst + delete by id once you’ve verified org ownership.
  • verifierId is optional in the signature but always passed into id: verifierId. If it’s ever undefined, that’s invalid for a unique ID.

A safer pattern:

- async getVerifierById(orgId: string, verifierId?: string): Promise<oid4vp_verifier | null> {
+ async getVerifierById(orgId: string, verifierId: string): Promise<oid4vp_verifier | null> {
  // ...
- const result = await this.prisma.oid4vp_verifier.findUnique({
-   where: {
-     id: verifierId,
-     orgAgent: {
-       orgId
-     }
-   }
- });
+ const result = await this.prisma.oid4vp_verifier.findFirst({
+   where: {
+     id: verifierId,
+     orgAgent: { orgId }
+   }
+ });
- async deleteVerifierByVerifierId(orgId: string, verifierId?: string): Promise<oid4vp_verifier | null> {
+ async deleteVerifierByVerifierId(orgId: string, verifierId: string): Promise<oid4vp_verifier | null> {
  // ...
- const deleted = await this.prisma.oid4vp_verifier.delete({
-   where: {
-     id: verifierId,
-     orgAgent: {
-       orgId
-     }
-   }
- });
+ const record = await this.prisma.oid4vp_verifier.findFirst({
+   where: {
+     id: verifierId,
+     orgAgent: { orgId }
+   }
+ });
+
+ if (!record) {
+   this.logger.warn(
+     `[deleteVerifierByVerifierId] No verifier found for orgId=${orgId}, verifierId=${verifierId}`
+   );
+   return null;
+ }
+
+ const deleted = await this.prisma.oid4vp_verifier.delete({
+   where: { id: record.id }
+ });

Also, the getVerifierById catch block still logs the wrong method name:

- this.logger.error(`[getVerifierById] Error in getVerifiersByPublicVerifierId: ${error.message}`);
+ this.logger.error(`[getVerifierById] Error in getVerifierById: ${error.message}`);
apps/api-gateway/src/verification/interfaces/verification.interface.ts (1)

3-9: Align proof request interfaces with other modules (optional vs required, and names type)

This file’s proof request types still differ from the versions in apps/verification and libs/common:

  • IProofRequestAttribute here makes attributeName, credDefId, and credentialName required, and omits attributeNames and schemaId.
  • IRequestedAttributesName.names is string, but in other places it’s string[].

These inconsistencies will make it hard to reuse shared code and can introduce subtle bugs at the API boundary.

To keep everything compatible, consider aligning with the other definitions:

-export interface IProofRequestAttribute {
-  attributeName: string;
-  condition?: string;
-  value?: string;
-  credDefId: string;
-  credentialName: string;
-}
+export interface IProofRequestAttribute {
+  attributeName?: string;
+  attributeNames?: string[];
+  condition?: string;
+  value?: string;
+  credDefId?: string;
+  schemaId?: string;
+  credentialName?: string;
+}
 interface IRequestedAttributesName {
   name?: string;
-  names?: string;
+  names?: string[];
   restrictions: IRequestedRestriction[];
 }

Also applies to: 62-66

apps/api-gateway/src/dtos/credential-offer.dto.ts (1)

10-38: Flatten @ApiProperty examples to match actual field shapes

The IssueCredentialOffer examples are still wrapped in extra objects, e.g.:

@ApiProperty({ example: { protocolVersion: 'v1' } })
protocolVersion: string;

But the field itself is just a string, so Swagger will show a slightly confusing example. Same pattern applies to attributes, credentialDefinitionId, autoAcceptCredential, comment, and connectionId.

Suggested adjustments:

-  @ApiProperty({ example: { protocolVersion: 'v1' } })
+  @ApiProperty({ example: 'v1' })
  protocolVersion: string;

-  @ApiProperty({ example: { attributes: [{ value: 'string', name: 'string' }] } })
+  @ApiProperty({ example: [{ value: 'string', name: 'string' }] })
  attributes: attributeValue[];

-  @ApiProperty({ example: { credentialDefinitionId: 'string' } })
+  @ApiProperty({ example: 'string' })
  credentialDefinitionId: string;

-  @ApiProperty({ example: { autoAcceptCredential: 'always' } })
+  @ApiProperty({ example: 'always' })
  autoAcceptCredential: string;

-  @ApiProperty({ example: { comment: 'string' } })
+  @ApiProperty({ example: 'string' })
  comment: string;

-  @ApiProperty({ example: { connectionId: '3fa85f64-5717-4562-b3fc-2c963f66afa6' } })
+  @ApiProperty({ example: '3fa85f64-5717-4562-b3fc-2c963f66afa6' })
  connectionId: string;
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts (1)

156-179: Add validation and API metadata for IssuerUpdationDto.issuerId

IssuerUpdationDto.issuerId is currently undecorated, so non‑string or malformed values can bypass validation and reach the service layer. This matches the previously raised concern and is still unresolved.

You can align it with the creation DTO and keep it optional:

@ApiPropertyOptional({
  description: 'Optional override for the issuer identifier',
  example: 'credebl-university'
})
@IsOptional()
@IsString({ message: 'issuerId must be a string' })
issuerId?: string;

Also, you can drop the extra @ApiProperty on batchCredentialIssuanceSize and keep only @ApiPropertyOptional to avoid duplicate/contradictory Swagger metadata.

Please confirm this change still matches your update semantics and runs cleanly with your existing validation pipeline.

apps/oid4vc-verification/test/app.e2e-spec.ts (1)

18-18: Remove the HTTP test—this is a microservices-only module without HTTP routes.

Based on the pattern across the PR, the Oid4vcVerificationModule is configured exclusively for NATS message-driven communication. The controller likely contains only @MessagePattern handlers, not HTTP route decorators. The test attempts a GET request to "/", which does not exist. The test will fail.

Either remove this test file if HTTP testing isn't needed for this module, or refactor the controller to include actual HTTP routes and update the test accordingly.

apps/oid4vc-issuance/test/app.e2e-spec.ts (1)

18-18: Remove the HTTP test—this is a microservices-only module without HTTP routes.

The Oid4vcIssuanceModule is configured exclusively for NATS message-driven communication (via ClientsModule.register() with Transport.NATS). The Oid4vcIssuanceController contains only @MessagePattern handlers, not HTTP route decorators. The test attempts a GET request to "/", which does not exist. The test will fail.

Either remove this test file if HTTP testing isn't needed for this module, or refactor the controller to include actual HTTP routes and update the test accordingly.

.env.sample (1)

126-127: Correct the service names in comments.

Lines 126 and 127 incorrectly reference "x509 service" in their comments. These should reference the OIDC4VC issuance and verification services respectively.

Apply this diff:

-OIDC4VC_ISSUANCE_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
-OIDC4VC_VERIFICATION_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
+OIDC4VC_ISSUANCE_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for OIDC4VC issuance service
+OIDC4VC_VERIFICATION_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for OIDC4VC verification service
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.module.ts (1)

17-17: Critical: NATS service name mismatch prevents gateway-microservice communication.

This gateway module uses CommonConstants.ISSUANCE_SERVICE, but the OID4VC issuance microservice at apps/oid4vc-issuance/src/main.ts:15 uses CommonConstants.OIDC4VC_ISSUANCE_SERVICE. The mismatch will prevent proper NATS message routing between the gateway and microservice.

Apply this diff:

-        options: getNatsOptions(CommonConstants.ISSUANCE_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
+        options: getNatsOptions(CommonConstants.OIDC4VC_ISSUANCE_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
libs/common/src/nats.config.ts (2)

17-18: Critical: Removing NATS queue group breaks load balancing across all microservices.

Commenting out the queue assignment fundamentally changes NATS message distribution. Without queue groups, every instance of each microservice receives every message (broadcast) instead of load-balanced distribution. This affects 30+ services and will cause duplicate message processing when multiple instances run.

Apply this diff to restore queue-based load balancing:

     servers: `${process.env.NATS_URL}`.split(','),
     maxReconnectAttempts: NATSReconnects.maxReconnectAttempts,
-    reconnectTimeWait: NATSReconnects.reconnectTimeWait
-    // queue: serviceName
+    reconnectTimeWait: NATSReconnects.reconnectTimeWait,
+    queue: serviceName

If broadcast behavior is intentional, document this architectural decision in code comments and repository documentation.


17-17: Fix syntax error: Missing trailing comma.

Line 17 is missing a trailing comma after reconnectTimeWait, which will cause a syntax error in JavaScript/TypeScript.

Apply this diff:

-    reconnectTimeWait: NATSReconnects.reconnectTimeWait
+    reconnectTimeWait: NATSReconnects.reconnectTimeWait,
libs/prisma-service/prisma/migrations/20250822104325_oidc_issuer_templates/migration.sql (1)

1-2: Misleading comment: Migrations apply in all environments, not just dev.

The comment "safe for dev" incorrectly suggests this migration only affects development environments. Prisma migrations apply to all environments (development, staging, production) based on your migration tracking. The DROP TABLE IF EXISTS is appropriate for initial table creation, but the comment should be clarified.

Replace the comment with:

-- Initial table creation (dropping any stale table if it exists)
apps/api-gateway/src/x509/x509.module.ts (1)

17-24: Fix incorrect module placement in ClientsModule.register().

CommonModule is incorrectly placed inside the ClientsModule.register() array. The register() method expects client configuration objects (with name, transport, options properties), not module imports. This will cause a runtime error.

Apply this diff to fix the module placement:

   imports: [
     HttpModule,
     ConfigModule.forRoot(),
+    CommonModule,
     ClientsModule.register([
       {
         name: 'NATS_CLIENT',
         transport: Transport.NATS,
         options: getNatsOptions(CommonConstants.X509_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
-      },
-      CommonModule
+      }
     ])
   ],
libs/prisma-service/prisma/migrations/20250902082321_add_org_agent_id_to_oidc_issuer/migration.sql (1)

1-21: Migration requires backfill strategy and FK restoration.

This migration has the same critical issues previously identified:

  1. NOT NULL columns without defaults: Adding orgAgentId UUID NOT NULL and publicIssuerId TEXT NOT NULL without defaults or backfill will fail on non-empty tables
  2. Dropped referential integrity: Removing oidc_issuer_createdBy_fkey leaves createdBy unconstrained

The migration needs:

  1. A backfill strategy: add columns as nullable, populate from agentIssuerId or other sources, then tighten to NOT NULL
  2. FK restoration or clear documentation if createdBy constraint removal is intentional
apps/issuance/constant/issuance.ts (1)

1-6: Code duplication detected with apps/oid4vc-issuance/constant/issuance.ts.

These constants (lines 3-6) duplicate those in apps/oid4vc-issuance/constant/issuance.ts. This duplication was already flagged in the review of that file. Consider consolidating into a shared module.

apps/verification/src/verification.controller.ts (1)

48-48: Replace overly broad object return type with specific interface.

The return type Promise<object> was already flagged in a previous review. Use Promise<IProofPresentationDetails> or a similar specific interface for type safety.

Also applies to lines 71, 82.

libs/common/src/date-only.ts (1)

5-24: Constructor validation and normalization issues remain.

The issues flagged in the previous review are still present:

  1. Line 18: Regex /^\d{4}-\d{2}-\d{2}(T.*Z)?$/ rejects valid ISO timestamps with timezone offsets (e.g., 2025-11-17T10:00:00+01:00)
  2. Lines 18-19: Convoluted iso || date fallback logic
  3. Lines 13-15: new Date() captures time-of-day, inconsistent with "date-only" semantics
  4. No normalization to UTC midnight

Please address these issues as outlined in the previous review comment.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts (1)

240-253: Missing required field validation for template.

The template field lacks @IsDefined() validation, allowing payloads that omit it to pass validation. This causes runtime failures when downstream code attempts to dereference the template structure.

Please add the @IsDefined() decorator as outlined in the previous review comment.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)

34-37: Enable nested validation for credentialOfferPayload.

The credentialOfferPayload field is missing @ValidateNested() and @Type() decorators, preventing validation of the nested credential_configuration_ids array constraints. Malformed payloads will pass validation, potentially causing downstream errors.

Please add the decorators as outlined in the previous review comment.

apps/api-gateway/src/x509/x509.service.ts (1)

55-64: Correct return type for paginated list method.

The getX509CertificatesByOrgId method is typed to return Promise<X509CertificateRecord> (a single record), but the X509 microservice returns a paginated envelope { data: X509CertificateRecord[], total: number }. This type mismatch erodes type safety and may lead to runtime errors in consuming code.

Please update the return type as outlined in the previous review comment.

apps/oid4vc-verification/src/oid4vc-verification.service.ts (3)

43-98: Avoid dereferencing createdVerifierDetails in 409 conflict path

In oid4vpCreateVerifier, the inner catch does:

let createdVerifierDetails;
...
try {
  createdVerifierDetails = await this._createOid4vpVerifier(...);
  ...
} catch (error) {
  const status409 = ...;
  if (status409) {
    throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
  }
  throw error;
}

If the agent returns a 409 before we ever set createdVerifierDetails, this dereference throws and masks the intended conflict.

Use the known verifierId from the request instead:

-      let createdVerifierDetails;
+      let createdVerifierDetails: VerifierRecord | undefined;
...
       try {
         createdVerifierDetails = await this._createOid4vpVerifier(createVerifier, url, orgId);
@@
       } catch (error) {
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;

         if (status409) {
-          throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
+          throw new ConflictException(`Verifier with id '${verifierId}' already exists`);
         }
         throw error;
       }

This preserves the correct 409 response without introducing a secondary error.


100-158: Fix empty-verifier guard and update conflict handling in oid4vpUpdateVerifier

Two problems in oid4vpUpdateVerifier:

  1. The guard never triggers:
const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
if (0 > existingVerifiers.length) {
  throw new NotFoundException(...);
}

existingVerifiers.length is 0 or more, so 0 > length is always false, and you can then dereference existingVerifiers[0] even when the array is empty.

  1. In the inner catch, the 409 path dereferences a possibly-undefined updatedVerifierDetails:
if (status409) {
  throw new ConflictException(`Verifier with id '${updatedVerifierDetails.verifierId}' already exists`);
}

If the agent rejects the update before returning a body, updatedVerifierDetails is undefined and this throws a new error.

Suggested fix:

-      let updatedVerifierDetails;
-      const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 > existingVerifiers.length) {
+      let updatedVerifierDetails;
+      const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
+      if (!existingVerifiers || existingVerifiers.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
@@
       try {
         updatedVerifierDetails = await this._updateOid4vpVerifier(updateVerifier, url, orgId);
@@
       } catch (error) {
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;

         if (status409) {
-          throw new ConflictException(`Verifier with id '${updatedVerifierDetails.verifierId}' already exists`);
+          const conflictId = existingVerifiers?.[0]?.publicVerifierId ?? verifierId;
+          throw new ConflictException(`Verifier with id '${conflictId}' already exists`);
         }
         throw error;
       }

This correctly throws NotFound when no verifier exists and avoids dereferencing updatedVerifierDetails in the conflict path.


207-284: Fix X.509 signer lookup: use the real method instead of methodv

In the X.509 branch of oid4vpCreateVerificationSession:

} else if (
  sessionRequest.requestSigner.method === SignerOption.X509_P256 ||
  sessionRequest.requestSigner.method === SignerOption.X509_ED25519
) {
  ...
  const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
    orgId,
    sessionRequest.requestSigner.methodv
  );

sessionRequest.requestSigner.methodv does not exist and will always be undefined, so getCurrentActiveCertificate never finds a matching record and you always hit the “No active certificate” path.

Use the actual method value:

-        const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
-          orgId,
-          sessionRequest.requestSigner.methodv
-        );
+        const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
+          orgId,
+          sessionRequest.requestSigner.method
+        );
@@
-          throw new NotFoundException(
-            `No active certificate(${sessionRequest.requestSigner.method}}) found for issuer`
-          );
+          throw new NotFoundException(
+            `No active certificate(${sessionRequest.requestSigner.method}) found for issuer`
+          );

You may also want to explicitly map from SignerOption to the x5cKeyType used in your X.509 store if those enums differ.

apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts (1)

93-98: Type IssuerUpdation.display instead of leaving it as any

IssuerUpdation.display currently has no type annotation:

export interface IssuerUpdation {
  issuerId: string;
  accessTokenSignerKeyType: AccessTokenSignerKeyType;
  display;
  batchCredentialIssuanceSize?: number;
}

That makes it implicitly any and removes type-safety for one of the most important fields in issuer updates, increasing the risk of malformed data passing through.

Given you already have a Display interface above, consider:

 export interface IssuerUpdation {
   issuerId: string;
   accessTokenSignerKeyType: AccessTokenSignerKeyType;
-  display;
+  display?: Display[];
   batchCredentialIssuanceSize?: number;
 }

(or make it non-optional if updates must always carry a display array). This was flagged previously and still looks worth fixing before depending on these interfaces widely.

id?: string;
clientSocketId?: string;
agentEndPoint?: string;
agentToken?: string;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify usage of the new agentToken field.

The addition of the optional agentToken field to IStoreOrgAgent is backward compatible. Ensure that the new field is properly utilized in the agent storage and retrieval logic.


🏁 Script executed:

#!/bin/bash
# Verify where agentToken is being used in the codebase
rg -n "agentToken" --type=ts -C3

Length of output: 5778


I need to verify the storage and retrieval logic to ensure the new field is properly handled in the database layer.


🏁 Script executed:

#!/bin/bash
# Check for database/storage operations related to IStoreOrgAgent or agent details storage
rg -n "IStoreOrgAgent|saveAgent|updateAgent|getAgent|storeOrgAgent" --type=ts -C2 | head -100

Length of output: 7615


🏁 Script executed:

#!/bin/bash
# Check for database schema or entity definitions related to agents
fd -e ts -e js | xargs grep -l "agent.*schema\|agent.*entity" --include="*.ts" 2>/dev/null | head -10

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Search for where IStoreOrgAgent interface is being implemented or used
rg -n "IStoreOrgAgent" --type=ts -B2 -A5

Length of output: 9243


I need to examine the complete interface definitions and the storage implementation more carefully.


🏁 Script executed:

#!/bin/bash
# Get the complete IStoreOrgAgentDetails interface definition
rg -n "export interface IStoreOrgAgentDetails" --type=ts -A30

Length of output: 2632


🏁 Script executed:

#!/bin/bash
# Get the complete storeOrgAgentDetails method implementation
rg -n "async storeOrgAgentDetails" --type=ts -A20

Length of output: 2135


🏁 Script executed:

#!/bin/bash
# Check if agentToken is used in the agent payload construction (line 552 area)
rg -n "agentPayload: IStoreOrgAgentDetails" --type=ts -B5 -A15

Length of output: 1909


I found a critical issue. Let me verify how agent details are retrieved to confirm the storage gap.


🏁 Script executed:

#!/bin/bash
# Find where agentDetails is retrieved (lines 543, 576 context)
rg -n "agentDetails\s*=" --type=ts -B3 -C2 | head -60

Length of output: 5341


🏁 Script executed:

#!/bin/bash
# Check for methods that retrieve agent data from database
rg -n "getOrgAgent|findAgent|getAgent\(" --type=ts -C2 | grep -A5 "repository\|prisma" | head -40

Length of output: 2816


🏁 Script executed:

#!/bin/bash
# Check if org_agents Prisma schema includes agentToken column
fd -name "schema.prisma" -o -name "*prisma*" | xargs cat 2>/dev/null | grep -A30 "model org_agents"

Length of output: 230


Let me search for the Prisma schema file more carefully:


🏁 Script executed:

#!/bin/bash
# Find Prisma schema file
find . -name "schema.prisma" -type f 2>/dev/null

Length of output: 42


🏁 Script executed:

#!/bin/bash
# If found, display org_agents model
find . -name "schema.prisma" -type f 2>/dev/null | head -1 | xargs cat | grep -A 40 "model org_agents"

Length of output: 3636


🏁 Script executed:

#!/bin/bash
# Alternative: search for prisma directory structure
find . -path "*/prisma/schema.prisma" -o -path "*/libs/prisma/*" -type f 2>/dev/null | head -5

Length of output: 42


Add agentToken field to database schema and storage interface to complete integration.

The new agentToken field in IStoreOrgAgent is not properly persisted to the database. The Prisma org_agents model lacks the agentToken column, and the storage interface IStoreOrgAgentDetails does not include this field. This breaks storage and retrieval:

  • storeOrgAgentDetails() in apps/agent-service/src/repositories/agent-service.repository.ts:158 receives IStoreOrgAgentDetails (which lacks agentToken), so the field is never saved
  • When agent details are retrieved from the database, agentToken will be undefined
  • Currently agentToken is read from files (apps/agent-provisioning/src/agent-provisioning.service.ts:71), not the database

Required fixes:

  1. Add agentToken column to org_agents model in libs/prisma-service/prisma/schema.prisma
  2. Add agentToken?: string; field to IStoreOrgAgentDetails interface in apps/agent-service/src/interface/agent-service.interface.ts
  3. Include agentToken in the payload at apps/agent-service/src/agent-service.service.ts:552-567 when storing agent details
🤖 Prompt for AI Agents
In apps/agent-service/src/interface/agent-service.interface.ts around line 216,
add the missing agentToken?: string field to IStoreOrgAgentDetails so the
interface includes the token; in libs/prisma-service/prisma/schema.prisma update
the org_agents model to add an agentToken column (string, optional) and run
prisma migrate to update the DB schema; and in
apps/agent-service/src/agent-service.service.ts around lines 552-567 include
agentToken from the incoming data into the payload passed to
storeOrgAgentDetails() so the token is persisted.

Comment on lines +243 to +301
export class SignerOptionsDto {
@IsString()
@IsIn(['did', 'x5c'], { message: 'method must be either "did" or "x5c"' })
method: string;

@IsString()
@IsOptional()
did?: string;

@IsArray()
@IsOptional()
x5c?: string[];
}

export class CredentialDto {
@ApiProperty({
description: 'Unique ID of the supported credential',
example: 'DrivingLicenseCredential-mdoc'
})
@IsString()
credentialSupportedId: string;

@ApiProperty({
description: 'Signer options for credential issuance',
example: {
method: 'x5c',
x5c: [
'MIIB3jCCAZCgAwIBAgIQQfBdIK9v3TIzKR+1HjlixDAFBgMrZXAwJDEUMBIGA1UEAxMLRFkgdGVzdCBvcmcxDDAKBgNVBAYTA0lORDAeFw0yNTA5MjQwMDAwMDBaFw0yODA5MjQwMDAwMDBaMCQxFDASBgNVBAMTC0RZIHRlc3Qgb3JnMQwwCgYDVQQGEwNJTkQwKjAFBgMrZXADIQDIkLycOlkHP6+MG4rprj8fyxRfwqhH8Xx9v0XxCd175aOB1zCB1DAdBgNVHQ4EFgQUbqjjbQgbAx3lPjkPBVQwvvF14agwDgYDVR0PAQH/BAQDAgGGMBUGA1UdJQEB/wQLMAkGByiBjF0FAQIwOwYDVR0SBDQwMoIXaHR0cDovL3Rlc3QuZXhhbXBsZS5jb22GF2h0dHA6Ly90ZXN0LmV4YW1wbGUuY29tMDsGA1UdEQQ0MDKCF2h0dHA6Ly90ZXN0LmV4YW1wbGUuY29thhdodHRwOi8vdGVzdC5leGFtcGxlLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMAUGAytlcANBALTqC64XSTRUoMmwYbCD/z46U/Je6IeQsh6qq4qXh+wfnMIfJMvLQnG+nMkfeAs3zYAwjK6sCZ/7lHkEJnYObQ4='
]
}
})
@ValidateNested()
@Type(() => SignerOptionsDto)
signerOptions: SignerOptionsDto;

@ApiProperty({
description: 'Credential format type',
enum: ['mso_mdoc', 'vc+sd-jwt'],
example: 'mso_mdoc'
})
@IsString()
@IsIn(['mso_mdoc', 'vc+sd-jwt'], { message: 'format must be either "mso_mdoc" or "vc+sd-jwt"' })
format: string;

@ApiProperty({
description: 'Credential payload (namespace data, validity info, etc.)',
example: [
{
namespaces: {
'org.iso.23220.photoID.1': {
birth_date: '1970-02-14',
family_name: 'Müller-Lüdenscheid',
given_name: 'Ford Praxibetel',
document_number: 'LA001801M'
}
},
validityInfo: {
validFrom: '2025-04-23T14:34:09.188Z',
validUntil: '2026-05-03T14:34:09.188Z'
}
},
{
full_name: 'Garry',
address: {
street_address: 'M.G. Road',
locality: 'Pune',
country: 'India'
},
iat: 1698151532,
nbf: dateToSeconds(new Date()),
exp: dateToSeconds(new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000))
}
]
})
@ValidateNested()
payload: object;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SignerOptionsDto / CredentialDto: address ESLint hints and nested payload validation

Functionally these DTOs look fine, but:

  • ESLint flags the x5c example array for array-bracket-newline. You can satisfy it by putting the element on the same line:
x5c: ['MIIB3jCCAZC...']
  • ESLint also flags the dateToSeconds(new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000)) example for mixed +/*. Adding parentheses clarifies intent:
exp: dateToSeconds(new Date(Date.now() + (5 * 365 * 24 * 60 * 60 * 1000)))

Also, payload is annotated with @ValidateNested() but not @Type or a concrete class type; if you don’t actually need nested validation here, you could drop @ValidateNested() to avoid confusion, or define a dedicated payload DTO if you do.

🧰 Tools
🪛 ESLint

[error] 269-269: There should be no linebreak after '['.

(array-bracket-newline)


[error] 271-271: There should be no linebreak before ']'.

(array-bracket-newline)


[error] 313-313: Unexpected mix of '+' and '*'. Use parentheses to clarify the intended order of operations.

(no-mixed-operators)


[error] 313-313: Unexpected mix of '+' and '*'. Use parentheses to clarify the intended order of operations.

(no-mixed-operators)

🤖 Prompt for AI Agents
In apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts around lines
243-319, address three issues: 1) fix the ESLint array-bracket-newline warning
by collapsing the long x5c example into a single-line array element; 2) fix the
mixed operator ESLint warning by wrapping the multiplier expression in
parentheses when computing the exp example (e.g. (5 * 365 * 24 * 60 * 60 *
1000)); 3) resolve the misleading @ValidateNested() on payload by either
removing @ValidateNested() if no nested validation is required, or define a
concrete payload DTO class and add @Type(() => PayloadDto) (and annotate payload
with that class) to enable proper nested validation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +468 to +500
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async deleteTemplate(templateId: string, orgId: string, userDetails: user, issuerId: string): Promise<any> {
try {
const template = await this.oid4vcIssuanceRepository.getTemplateById(templateId);
if (!template) {
throw new NotFoundException(ResponseMessages.oidcTemplate.error.notFound);
}
const deleteTemplate = await this.oid4vcIssuanceRepository.deleteTemplate(templateId);
if (!deleteTemplate) {
throw new NotFoundException(ResponseMessages.oidcTemplate.error.deleteTemplate);
}

const issuerTemplateConfig = await this.buildOidcIssuerConfig(issuerId);
const agentDetails = await this.oid4vcIssuanceRepository.getAgentEndPoint(orgId);
if (!agentDetails) {
throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
}
const { agentEndPoint } = agentDetails;
const issuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(issuerId);
if (!issuerDetails) {
throw new NotFoundException(ResponseMessages.oidcTemplate.error.issuerDetailsNotFound);
}
const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_ISSUER_TEMPLATE, issuerDetails.publicIssuerId);

const createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId);
if (!createTemplateOnAgent) {
throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
}
return deleteTemplate;
} catch (error) {
this.logger.error(`[deleteTemplate] - error: ${JSON.stringify(error)}`);
throw new RpcException(error.response ?? error);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Handle agent failure when deleting templates to keep DB and agent in sync.

deleteTemplate deletes the DB row first and only then updates the agent. If the agent call fails, there is no rollback, leaving the agent and DB inconsistent (unlike createTemplate and updateTemplate, which compensate).

Consider either:

  • Flipping the order (update agent first, then delete DB), or
  • Adding a rollback path that recreates the template in the DB when _createOIDCTemplate fails.

Comment on lines +624 to +648
async createOidcCredentialOfferD2A(oidcCredentialD2APayload, orgId: string): Promise<object | string> {
try {
for (const credential of oidcCredentialD2APayload.credentials) {
const { signerOptions } = credential;

if (!signerOptions?.method) {
throw new BadRequestException(`signerOptions.method is required`);
}
if (signerOptions.method === SignerMethodOption.X5C) {
if (!signerOptions.x5c || 0 === signerOptions.x5c.length) {
// const x5cFromDb = await this.oid4vcIssuanceRepository.getIssuerX5c(
const x5cFromDb = 'Test';
// If you want to use the actual DB call, uncomment and use:
// const x5cFromDb = await this.oid4vcIssuanceRepository.getIssuerX5c(
// oidcCredentialD2APayload.publicIssuerId,
// orgId
// );
if (!x5cFromDb || 0 === x5cFromDb.length) {
throw new BadRequestException(`No x5c found for issuer`);
}
signerOptions.x5c = x5cFromDb;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove hard-coded 'Test' x5c stub in createOidcCredentialOfferD2A.

For X5C signer options, you currently inject a static 'Test' value:

const x5cFromDb = 'Test';
...
signerOptions.x5c = x5cFromDb;

This breaks real flows and hides the intended DB lookup. Either:

  • Wire up the actual repository call (as per the commented-out code), or
  • Fail fast when signerOptions.x5c is missing and the DB lookup is not yet implemented.

Leaving this stub in will produce invalid credential offers in any path relying on X.509.

🤖 Prompt for AI Agents
In apps/oid4vc-issuance/src/oid4vc-issuance.service.ts around lines 624-645,
remove the hard-coded const x5cFromDb = 'Test' stub and either invoke the
commented repository call to fetch the issuer x5c (await
this.oid4vcIssuanceRepository.getIssuerX5c(oidcCredentialD2APayload.publicIssuerId,
orgId)) and assign that to signerOptions.x5c, or if the DB integration is not
ready, immediately throw a BadRequestException when signerOptions.x5c is missing
instead of inserting a fake value; ensure you preserve the existing check that
throws if the retrieved x5c is empty.

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 43

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/api-gateway/src/connection/connection.service.ts (1)

31-49: Apply consistent error handling pattern across both methods.

The sendQuestion method now uses error?.response ?? error (line 35), but sendBasicMessage still uses error.response directly (line 47). This inconsistency means sendBasicMessage could throw an error with undefined if error.response doesn't exist.

Apply this diff to align the error handling:

   sendBasicMessage(basicMessageDto: BasicMessageDto): Promise<object> {
     try {
       return this.natsClient.sendNatsMessage(
         this.connectionServiceProxy,
         'send-basic-message-on-connection',
         basicMessageDto
       );
     } catch (error) {
-      throw new RpcException(error.response);
+      throw new RpcException(error?.response ?? error);
     }
   }
apps/cloud-wallet/src/cloud-wallet.service.ts (2)

151-151: Fix URL syntax error - extra closing brace.

The URL construction contains an extra closing brace that will cause the API request to fail.

Apply this diff to fix the syntax error:

-      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}}`;
+      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}`;

173-173: Fix URL syntax error - extra closing brace.

The URL construction contains an extra closing brace that will cause the API request to fail.

Apply this diff to fix the syntax error:

-      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${threadParam}}`;
+      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${threadParam}`;
apps/verification/src/verification.service.ts (1)

65-69: Remove unused injected dependencies.

Verification confirms that userActivityService (line 66) and cacheService (line 69) are not used anywhere in this service. Remove both to eliminate unnecessary coupling:

    private readonly userActivityService: UserActivityService,
    @Inject(CACHE_MANAGER) private readonly cacheService: Cache,

Delete these two lines from the constructor.

♻️ Duplicate comments (33)
apps/api-gateway/src/issuance/issuance.service.ts (1)

29-29: Import reordering looks good; confirm past ESLint disable is resolved.

The Prisma user type import is correctly positioned and used consistently in method signatures (e.g., line 261). All methods in the file already have explicit return type annotations, so the import change does not introduce any ESLint violations.

However, per the past review comment, verify that the file-level ESLint disable for @typescript-eslint/explicit-function-return-type (if it existed at lines 2–3) has been removed. I don't see it in the current code, so this may be already addressed.

apps/api-gateway/src/dtos/credential-offer.dto.ts (1)

10-38: Fix ApiProperty examples - remove unnecessary object wrapping.

The @ApiProperty examples remain incorrectly wrapped in objects. For instance, { protocolVersion: 'v1' } should be 'v1', and { attributes: [...] } should be [...]. This affects the generated OpenAPI/Swagger documentation.

Please apply the fix suggested in the previous review to unwrap each example value.

apps/utility/src/utilities.repository.ts (1)

31-45: Missing null handling and type annotation (previously flagged).

The issues identified in the previous review remain unaddressed:

  1. Critical: Missing null handling - findUnique returns null when no record is found, but the service layer accesses properties on the result without checking, causing runtime errors.
  2. Missing type annotation - The referenceId parameter lacks an explicit type.

Fix 1: Add null handling

   async getShorteningUrl(referenceId): Promise<shortening_url> {
     try {
       const storeShorteningUrl = await this.prisma.shortening_url.findUnique({
         where: {
           referenceId
         }
       });
 
+      if (!storeShorteningUrl) {
+        throw new Error(`Shortening URL not found for referenceId: ${referenceId}`);
+      }
+
       this.logger.log(`[getShorteningUrl] - shortening url details ${referenceId}`);
       return storeShorteningUrl;

Fix 2: Add type annotation

-  async getShorteningUrl(referenceId): Promise<shortening_url> {
+  async getShorteningUrl(referenceId: string): Promise<shortening_url> {
apps/connection/src/connection.service.ts (2)

64-82: Persistent issue: Replace Promise<unknown> with a specific interface.

This is a repeat of a previous review comment. The return type Promise<unknown> at line 64 provides no type safety. Based on usage at lines 534, 540, and 557 where properties like invitationUrl, invitationDid, and outOfBandRecord.id are accessed, define a specific interface.

Consider creating an interface:

interface IConnectionInvitationResult {
  invitationUrl: string;
  invitationDid?: string;
  outOfBandRecord: { id: string };
}

Then update the return type:

-  async _createConnectionInvitation(connectionPayload: object, url: string, orgId: string): Promise<unknown> {
+  async _createConnectionInvitation(connectionPayload: object, url: string, orgId: string): Promise<IConnectionInvitationResult> {

572-595: Persistent issue: Replace Promise<any> with a specific interface.

This is a repeat of a previous review comment. The eslint-disable-next-line @typescript-eslint/no-explicit-any comment at line 576 indicates awareness of the type safety issue. Define a specific interface for the return type instead of using any.

Apply this pattern:

   async _createOutOfBandConnectionInvitation(
     connectionPayload: ICreateConnectionInvitation,
     url: string,
     orgId: string
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  ): Promise<any> {
+  ): Promise<IConnectionInvitationResult> {

Where IConnectionInvitationResult is the interface suggested in the previous comment.

apps/api-gateway/src/verification/interfaces/verification.interface.ts (2)

3-9: Interface inconsistency: IProofRequestAttribute has breaking changes and missing fields.

The issues previously identified still exist:

  • attributeName, credDefId, and credentialName are required but should be optional to match other definitions in the codebase
  • Missing fields: attributeNames?: string[] and schemaId?: string

62-66: Type error: names should be an array.

The issue previously identified still exists: names is typed as string but should be string[] to represent an array of attribute names (consistent with proof request patterns in the codebase).

Apply this diff:

 interface IRequestedAttributesName {
   name?: string;
-  names?: string;
+  names?: string[];
   restrictions: IRequestedRestriction[];
 }
apps/verification/src/verification.controller.ts (1)

48-48: Replace overly broad object return types with specific interfaces.

These methods return Promise<object> or Promise<object | object[]>, which removes type safety and IntelliSense support. This issue was previously flagged. Consider using specific interfaces from the codebase (e.g., IProofPresentationDetails, IProofPresentationList) to provide proper type contracts for API consumers.

Also applies to: 71-71, 82-82

Also applies to: 71-71, 82-82

apps/issuance/src/issuance.controller.ts (1)

1-2: Address ESLint disable directives as technical debt.

The blanket ESLint disables mask potential code quality issues across the entire controller. While the TODO comment acknowledges this, consider incrementally enabling rules and fixing violations method-by-method, or using granular disables only where justified.

libs/common/src/nats.config.ts (1)

17-18: Critical: NATS queue group removal affects message distribution.

Commenting out the queue assignment changes NATS to broadcast messages to all service instances instead of load-balanced distribution. This affects 30+ microservices and could cause duplicate message processing. Additionally, line 17 is missing a trailing comma after reconnectTimeWait.

Apply this diff to restore the queue assignment and fix the syntax:

-    reconnectTimeWait: NATSReconnects.reconnectTimeWait
-    // queue: serviceName
+    reconnectTimeWait: NATSReconnects.reconnectTimeWait,
+    queue: serviceName
apps/oid4vc-verification/src/oid4vc-verification.repository.ts (4)

42-57: Add null check for org_agents_type result.


101-120: Fix error message in catch block.


138-155: Fix inconsistent error messages in catch blocks.


157-174: Use findFirst instead of findUnique for multi-condition queries.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)

34-37: Enable nested validation for credentialOfferPayload.

apps/oid4vc-issuance/test/app.e2e-spec.ts (1)

18-18: Remove the HTTP test—this is a microservices-only module without HTTP routes.

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.module.ts (1)

17-17: Fix NATS service name to match the OIDC microservice.

apps/oid4vc-issuance/src/main.ts (1)

15-15: API gateway oid4vc-issuance module uses incorrect NATS service name.

apps/api-gateway/src/x509/x509.module.ts (1)

17-24: Fix incorrect module placement in ClientsModule.register().

CommonModule is incorrectly placed inside the ClientsModule.register() array. The register() method expects client configuration objects (with name, transport, options properties), not module imports. This will cause a runtime error.

Apply this diff to fix the module placement:

   imports: [
     HttpModule,
     ConfigModule.forRoot(),
+    CommonModule,
     ClientsModule.register([
       {
         name: 'NATS_CLIENT',
         transport: Transport.NATS,
         options: getNatsOptions(CommonConstants.X509_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
-      },
-      CommonModule
+      }
     ])
   ],
.env.sample (1)

126-127: Fix incorrect service references in NKEY seed comments.

Lines 126 and 127 both reference "x509 service" in their comments, but line 126 should reference "OIDC4VC issuance service" and line 127 should reference "OIDC4VC verification service". This documentation error could confuse developers configuring these environment variables.

Apply this diff to correct the comments:

-OIDC4VC_ISSUANCE_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
-OIDC4VC_VERIFICATION_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
+OIDC4VC_ISSUANCE_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for OIDC4VC issuance service
+OIDC4VC_VERIFICATION_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for OIDC4VC verification service
apps/oid4vc-issuance/constant/issuance.ts (1)

1-6: Code duplication with apps/issuance/constant/issuance.ts.

These constants duplicate definitions in apps/issuance/constant/issuance.ts. Consider consolidating into a shared location (e.g., libs/common/constants) to maintain a single source of truth and avoid drift between the two modules.

libs/common/src/cast.helper.ts (1)

527-542: Critical logic error: condition prevents function from working with arrays.

The condition on line 530 if (!queryParams || (queryParams?.length >= 0)) is fundamentally flawed:

  • For arrays: queryParams?.length >= 0 is always true (any array has length ≥ 0), causing the function to return early and never process array inputs.
  • For objects: While this evaluates to false and allows processing, the check doesn't validate whether the object is empty.

The likely intent was to check for null/undefined or empty objects.

Apply this diff to fix the logic and address ESLint issues:

-export function buildUrlWithQuery<T extends Record<string, any>>(baseUrl: string, queryParams: T): string {
+export function buildUrlWithQuery<T extends Record<string, unknown>>(baseUrl: string, queryParams: T): string {
   const criteriaParams: string[] = [];

-  if (!queryParams || (queryParams?.length >= 0)) {
-    return baseUrl
+  if (!queryParams || Object.keys(queryParams).length === 0) {
+    return baseUrl;
   }

   for (const [key, value] of Object.entries(queryParams)) {
     // Skip undefined or null values
-    if (value !== undefined && value !== null) {
+    if (undefined !== value && null !== value) {
       criteriaParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
     }
   }

-  return criteriaParams.length > 0 ? `${baseUrl}?${criteriaParams.join('&')}` : baseUrl;
+  return 0 < criteriaParams.length ? `${baseUrl}?${criteriaParams.join('&')}` : baseUrl;
 }

This fix:

  1. Properly checks for empty objects using Object.keys(queryParams).length === 0
  2. Adds missing semicolon
  3. Fixes Yoda conditions to satisfy ESLint
  4. Replaces any with unknown for better type safety
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier.dto.ts (1)

22-42: Align verifierId semantics between DTOs and controller.

CreateVerifierDto.verifierId is illustrated as 'verifier-12345' (non-UUID), but the controller’s GET /orgs/:orgId/oid4vp/verifier endpoint documents and validates verifierId as a UUID v4. Clients following this DTO example will hit validation errors on that GET/DELETE path.

Decide whether verifierId is a public identifier or an internal UUID and then:

  • either update this DTO’s description/example (and any other usages) to show a UUID, or
  • treat verifierId consistently as a public string in the controller (drop ParseUUIDPipe there and query by the public ID field).
apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (2)

157-237: Reconcile verifierId UUID validation with how verifier IDs are modeled.

getVerifierDetails validates the optional verifierId query param with ParseUUIDPipe({ version: '4', optional: true, ... }) and documents it as a UUID, but CreateVerifierDto shows verifierId as a non-UUID string (e.g. 'verifier-12345'). This mismatch will 400 clients that follow the DTO sample.

As previously suggested, decide whether this param is the internal UUID or a public identifier and then either:

  • keep ParseUUIDPipe and update DTOs/docs to use UUIDs, or
  • treat it as a string identifier (remove ParseUUIDPipe here and align repository lookups accordingly).

308-413: Use the shared IResponse envelope for presentation-related GETs.

getVerificationPresentation and getVerificationPresentationResponse currently return a custom envelope:

res.status(HttpStatus.OK).json({
  success: true,
  message: '...',
  data: result
});

while the rest of this controller uses the shared IResponse/IResponseType shape with statusCode and ResponseMessages.*. For consistency with existing consumers and ApiResponseDto, it’s better to:

  • build an IResponse object with statusCode, a message from ResponseMessages.oid4vpSession.*, and data: result, and
  • return it via res.status(HttpStatus.OK).json(finalResponse) as in the other methods.
apps/api-gateway/src/x509/x509.service.ts (1)

55-64: Align getX509CertificatesByOrgId return type with paginated response.

getX509CertificatesByOrgId is declared to return Promise<X509CertificateRecord>, but the X509 microservice controller responds with a paginated envelope { data: X509CertificateRecord[]; total: number }, and the REST controller treats record as that envelope. The current signature erodes type safety and can mislead consumers.

Consider updating the signature as follows (or aliasing a dedicated type):

-  async getX509CertificatesByOrgId(
-    orgId: string,
-    x509SearchCriteriaDto: X509SearchCriteriaDto,
-    reqUser: user
-  ): Promise<X509CertificateRecord> {
+  async getX509CertificatesByOrgId(
+    orgId: string,
+    x509SearchCriteriaDto: X509SearchCriteriaDto,
+    reqUser: user
+  ): Promise<{ data: X509CertificateRecord[]; total: number }> {

…and ensure any callers and DTOs expect the envelope rather than a single record.

libs/common/src/date-only.ts (1)

5-23: DateOnly constructor still mixes “date-only” semantics with full Date/time and has edge‑case parsing issues

Right now:

  • A Date keeps its time‑of‑day.
  • Missing/falsey strings like '' end up as “now”.
  • DateOnly instances are not normalized to a consistent point in time, which can produce different ISO dates across timezones and makes dateToSeconds harder to reason about.

To make this truly “date‑only” and align with prior feedback, normalize to UTC midnight and simplify parsing:

-  public constructor(date?: string | Date) {
-    if (date instanceof Date) {
-      if (isNaN(date.getTime())) {
-        throw new TypeError('Invalid Date');
-      }
-      this.date = date;
-      return;
-    }
-    if (!date) {
-      this.date = new Date();
-      return;
-    }
-    // Accept only YYYY-MM-DD or full ISO strings
-    const iso = /^\d{4}-\d{2}-\d{2}(T.*Z)?$/.test(date) ? date : '';
-    const d = new Date(iso || date);
-    if (isNaN(d.getTime())) {
-      throw new TypeError('Invalid date string');
-    }
-    this.date = d;
-  }
+  public constructor(date?: string | Date) {
+    let d: Date;
+
+    if (date instanceof Date) {
+      d = date;
+    } else if (!date) {
+      d = new Date();
+    } else {
+      // Parse ISO date string (YYYY-MM-DD) or full ISO timestamp
+      d = new Date(date);
+    }
+
+    if (isNaN(d.getTime())) {
+      throw new TypeError('Invalid date');
+    }
+
+    // Normalize to UTC midnight for date-only semantics
+    this.date = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
+  }

This also makes toISOString() and dateToSeconds much more predictable across environments.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts (1)

240-253: template is still not required; invalid payloads can slip through and break downstream

template is mandatory for both SD‑JWT and mDL templates, but there’s no @IsDefined() here. The validator will happily accept bodies without template, and your service code will then dereference undefined.

You can fix this by marking it as required before nested validation:

   @ApiProperty({
     type: Object,
     oneOf: [{ $ref: getSchemaPath(SdJwtTemplateDto) }, { $ref: getSchemaPath(MdocTemplateDto) }],
     description: 'Credential template definition (depends on credentialFormat)'
   })
+  @IsDefined({ message: 'template is required' })
   @ValidateNested()
   @Type(({ object }) => {
     if (object.format === CredentialFormat.Mdoc) {
       return MdocTemplateDto;
     } else if (object.format === CredentialFormat.SdJwtVc) {
       return SdJwtTemplateDto;
     }
   })
   template: SdJwtTemplateDto | MdocTemplateDto;
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts (1)

156-158: Add validation metadata for issuerId on update DTO

issuerId currently has no decorators, so any type (number/object) can flow through updates unchecked, and it also won’t be described in the OpenAPI schema. Mirroring the create DTO keeps update payloads well-formed.

 export class IssuerUpdationDto {
-  issuerId?: string;
+  @ApiPropertyOptional({
+    description: 'Optional override for the issuer identifier',
+    example: 'credebl-university'
+  })
+  @IsOptional()
+  @IsString({ message: 'issuerId must be a string' })
+  issuerId?: string;
apps/oid4vc-verification/src/oid4vc-verification.service.ts (3)

63-78: Fix conflict handling on verifier creation (avoid undefined dereference)

If the agent returns a 409 before a body is set, createdVerifierDetails is still undefined and the conflict path throws when accessing .verifierId, masking the intended 409 with a 500. Use the known verifierId from the request instead.

       try {
         createdVerifierDetails = await this._createOid4vpVerifier(createVerifier, url, orgId);
@@
       } catch (error) {
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;

         if (status409) {
-          throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
+          throw new ConflictException(`Verifier with id '${verifierId}' already exists`);
         }
         throw error;
       }

100-139: Guard empty verifier list and avoid using possibly-undefined updatedVerifierDetails

Two issues here:

  • if (0 > existingVerifiers.length) never triggers for an empty array, so you fall through and dereference existingVerifiers[0] when nothing exists.
  • In the conflict path you reference updatedVerifierDetails.verifierId, which is undefined if the agent rejected the update before returning data, causing a new error.
-      const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 > existingVerifiers.length) {
+      const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
+      if (!existingVerifiers || existingVerifiers.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
@@
       } catch (error) {
@@
-        if (status409) {
-          throw new ConflictException(`Verifier with id '${updatedVerifierDetails.verifierId}' already exists`);
-        }
+        if (status409) {
+          const conflictId = existingVerifiers?.[0]?.publicVerifierId ?? verifierId;
+          throw new ConflictException(`Verifier with id '${conflictId}' already exists`);
+        }
         throw error;
       }

207-259: Correct request signer key type usage for X.509 flows

In the X.509 branch you call getCurrentActiveCertificate with sessionRequest.requestSigner.methodv (always undefined) and your error message has a stray }. This breaks x5c-based flows and yields confusing errors.

-        const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
-          orgId,
-          sessionRequest.requestSigner.methodv
-        );
+        const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
+          orgId,
+          sessionRequest.requestSigner.method
+        );
@@
-        if (!activeCertificate) {
-          throw new NotFoundException(
-            `No active certificate(${sessionRequest.requestSigner.method}}) found for issuer`
-          );
-        }
+        if (!activeCertificate) {
+          throw new NotFoundException(
+            `No active certificate(${sessionRequest.requestSigner.method}) found for issuer`
+          );
+        }
apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts (1)

93-98: Type IssuerUpdation.display explicitly and keep it optional

display is currently untyped, so it becomes any and defeats compile-time validation for a core field in issuer updates. Making it an optional, properly typed property keeps updates safe and allows partial patches.

 export interface IssuerUpdation {
   issuerId: string;
   accessTokenSignerKeyType: AccessTokenSignerKeyType;
-  display;
+  display?: Display[];
   batchCredentialIssuanceSize?: number;
 }

Comment on lines +257 to +301
export class CredentialDto {
@ApiProperty({
description: 'Unique ID of the supported credential',
example: 'DrivingLicenseCredential-mdoc'
})
@IsString()
credentialSupportedId: string;

@ApiProperty({
description: 'Signer options for credential issuance',
example: {
method: 'x5c',
x5c: [
'MIIB3jCCAZCgAwIBAgIQQfBdIK9v3TIzKR+1HjlixDAFBgMrZXAwJDEUMBIGA1UEAxMLRFkgdGVzdCBvcmcxDDAKBgNVBAYTA0lORDAeFw0yNTA5MjQwMDAwMDBaFw0yODA5MjQwMDAwMDBaMCQxFDASBgNVBAMTC0RZIHRlc3Qgb3JnMQwwCgYDVQQGEwNJTkQwKjAFBgMrZXADIQDIkLycOlkHP6+MG4rprj8fyxRfwqhH8Xx9v0XxCd175aOB1zCB1DAdBgNVHQ4EFgQUbqjjbQgbAx3lPjkPBVQwvvF14agwDgYDVR0PAQH/BAQDAgGGMBUGA1UdJQEB/wQLMAkGByiBjF0FAQIwOwYDVR0SBDQwMoIXaHR0cDovL3Rlc3QuZXhhbXBsZS5jb22GF2h0dHA6Ly90ZXN0LmV4YW1wbGUuY29tMDsGA1UdEQQ0MDKCF2h0dHA6Ly90ZXN0LmV4YW1wbGUuY29thhdodHRwOi8vdGVzdC5leGFtcGxlLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMAUGAytlcANBALTqC64XSTRUoMmwYbCD/z46U/Je6IeQsh6qq4qXh+wfnMIfJMvLQnG+nMkfeAs3zYAwjK6sCZ/7lHkEJnYObQ4='
]
}
})
@ValidateNested()
@Type(() => SignerOptionsDto)
signerOptions: SignerOptionsDto;

@ApiProperty({
description: 'Credential format type',
enum: ['mso_mdoc', 'vc+sd-jwt'],
example: 'mso_mdoc'
})
@IsString()
@IsIn(['mso_mdoc', 'vc+sd-jwt'], { message: 'format must be either "mso_mdoc" or "vc+sd-jwt"' })
format: string;

@ApiProperty({
description: 'Credential payload (namespace data, validity info, etc.)',
example: [
{
namespaces: {
'org.iso.23220.photoID.1': {
birth_date: '1970-02-14',
family_name: 'Müller-Lüdenscheid',
given_name: 'Ford Praxibetel',
document_number: 'LA001801M'
}
},
validityInfo: {
validFrom: '2025-04-23T14:34:09.188Z',
validUntil: '2026-05-03T14:34:09.188Z'
}
},
{
full_name: 'Garry',
address: {
street_address: 'M.G. Road',
locality: 'Pune',
country: 'India'
},
iat: 1698151532,
nbf: dateToSeconds(new Date()),
exp: dateToSeconds(new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000))
}
]
})
@ValidateNested()
payload: object;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

CredentialDto.payload should be validated as a plain object instead of ValidateNested

payload is currently:

@ValidateNested()
payload: object;

Since object is not a DTO class and there is no @Type, ValidateNested is effectively a no-op and may confuse readers. If this is intended to be an arbitrary JSON object:

@IsObject()
payload: Record<string, unknown>;

If you need structured validation, consider defining a dedicated DTO and using @Type(() => ThatDto) instead.

🧰 Tools
🪛 ESLint

[error] 269-269: There should be no linebreak after '['.

(array-bracket-newline)


[error] 271-271: There should be no linebreak before ']'.

(array-bracket-newline)


[error] 313-313: Unexpected mix of '+' and '*'. Use parentheses to clarify the intended order of operations.

(no-mixed-operators)


[error] 313-313: Unexpected mix of '+' and '*'. Use parentheses to clarify the intended order of operations.

(no-mixed-operators)

🤖 Prompt for AI Agents
In apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts around lines
257-319, the payload property uses @ValidateNested() on a plain object which is
ineffective; replace the decorator and type to validate arbitrary JSON by using
@IsObject() and typing payload as Record<string, unknown>, or if the payload has
a specific structure create a dedicated DTO and use @ValidateNested() with
@Type(() => ThatDto) instead.

Comment on lines +1 to +7
/*
Warnings:
- You are about to drop the column `issuer` on the `credential_templates` table. All the data in the column will be lost.
- Added the required column `issuerId` to the `credential_templates` table without a default value. This is not possible if the table is not empty.
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Address the data migration risk for non-empty tables.

The warning on lines 4–5 indicates that issuerId is added as NOT NULL without a default value. This migration will fail if credential_templates already contains rows (which is possible if the first migration ran in any environment before this one).

The migration lacks a backfill strategy or seed data to populate issuerId before the constraint is applied.

Verify:

  • Whether credential_templates had any rows when this migration ran.
  • If a backfill/seed procedure exists to populate issuerId before applying the NOT NULL constraint.
  • Consider adding a data migration step (e.g., using Prisma's sql() helper or a separate backfill script) to handle existing rows.

I'll verify the data migration risk by examining the full migration file and checking for backfill strategies.


---

🏁 Script executed:

```shell
#!/bin/bash
# Read the full migration file
cat libs/prisma-service/prisma/migrations/20250822145801_fix_issuer_relation/migration.sql

Length of output: 1320


🏁 Script executed:

#!/bin/bash
# Search for seed files or backfill scripts
fd -t f -e seed -e backfill -e "migration" | head -20

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for any SQL backfill or UPDATE statements in migration files
rg -n "UPDATE.*credential_templates" libs/prisma-service/prisma/migrations/ || echo "No UPDATE found for credential_templates"

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Check if there are any seed scripts in the prisma directory
find libs/prisma-service -name "seed*" -o -name "*backfill*" | head -20

Length of output: 34


🏁 Script executed:

#!/bin/bash
# List all migrations to understand the sequence
ls -la libs/prisma-service/prisma/migrations/ 2>/dev/null | head -30

Length of output: 2515


🏁 Script executed:

#!/bin/bash
# Search for Prisma schema definition to understand the structure
cat libs/prisma-service/prisma/schema.prisma 2>/dev/null | grep -A 20 "credential_templates" || echo "Schema file or model not found"

Length of output: 1322


🏁 Script executed:

#!/bin/bash
# Check for any issuerId references in migrations or backfill logic
rg -n "issuerId" libs/prisma-service/prisma/migrations/ -C 2

Length of output: 2376


🏁 Script executed:

#!/bin/bash
# Look for sql() helpers or raw SQL in the migration that might do backfill
grep -r "sql\|UPDATE\|INSERT\|DEFAULT" libs/prisma-service/prisma/migrations/20250822145801_fix_issuer_relation/

Length of output: 803


Migration will fail on non-empty credential_templates table.

The migration file lacks any backfill logic. Line 10 adds issuerId as UUID NOT NULL without a default value and without an UPDATE statement to populate existing rows. This violates the NOT NULL constraint and will cause the migration to fail with a constraint violation error if any rows exist in credential_templates.

Additionally, the migration drops the issuer column (line 9) without preserving its data or mapping it to the new oidc_issuer table, making data recovery impossible after the migration fails or succeeds.

Required fix: Add an UPDATE statement (before the ADD COLUMN) to populate issuerId with a valid UUID from the oidc_issuer table, or provide a default mechanism (e.g., a migration-time value or backfill script) to satisfy the foreign key constraint.

attributes: attributeValue[];
@ApiProperty({ example: { attributes: [{ value: 'string', name: 'string' }] } })
@IsNotEmpty({ message: 'Please provide valid attributes' })
@IsArray({ message: 'attributes should be array' })
Copy link
Contributor

@GHkrishna GHkrishna Nov 18, 2025

Choose a reason for hiding this comment

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

Ig here we can add the @ArrayNotEmpty instead

Comment on lines 17 to 21
ClientsModule.register([
{
name: 'NATS_CLIENT',
transport: Transport.NATS,
options: getNatsOptions(CommonConstants.X509_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
},
CommonModule
])
Copy link
Contributor

Choose a reason for hiding this comment

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

Ig this is the reason why we might've needed to add CommonService as provider

this.logger.error('CRYPTO_PRIVATE_KEY is not configured');
throw new InternalServerErrorException('Encryption key is not configured');
}
const encryptedToken = CryptoJS.AES.encrypt(JSON.stringify(token), secret).toString();

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
an access to apiKey
is hashed insecurely.
Password from
an access to apiKey
is hashed insecurely.

Copilot Autofix

AI 19 days ago

To fix the problem, the secret should be protected using a proper key derivation function (e.g., bcrypt, scrypt, Argon2, or PBKDF2). If the intent is to securely hash API keys for storage/validation rather than to encrypt/decrypt them (e.g., to validate later, or store safely), use a slow/fixed-cost hash (e.g., bcrypt). If you need to be able to recover the original data (not just verify), then you must ensure your symmetric encryption is using a robust, unique key per secret (often derived with a KDF from a password), and a random initialization vector (IV); as is, a static key and lack of IV make it easy to brute force.

Assuming encryption is being used to store API keys safely without the requirement to retrieve (only to check validity), then using bcrypt to hash the API key and storing the hash is appropriate. The tokenEncryption function should use bcrypt to hash the API key and return the hash string. Since bcrypt does not have a decrypt function, only a compare, downstream code that expects to decrypt the API key (i.e., recover the original API key) would need review---but based on what's shown, this appears to only be about safe storage.

You should:

  • Replace the use of CryptoJS.AES.encrypt(JSON.stringify(token), secret).toString(); with a bcrypt hash.
  • Add the required import statement for bcrypt.
  • If you need interoperability (e.g., other parts of the code depend on decryption), further design changes may be needed outside what's visible.
  • Example: change tokenEncryption to return bcrypt.hash (async) result, using a salt (provided or generated).

Suggested changeset 1
apps/agent-service/src/agent-service.service.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts
--- a/apps/agent-service/src/agent-service.service.ts
+++ b/apps/agent-service/src/agent-service.service.ts
@@ -18,6 +18,7 @@
 import { ClientProxy, RpcException } from '@nestjs/microservices';
 import * as dotenv from 'dotenv';
 import * as fs from 'fs';
+import * as bcrypt from 'bcrypt';
 import { map } from 'rxjs/operators';
 dotenv.config();
 import {
@@ -2201,14 +2202,10 @@
 
   private async tokenEncryption(token: string): Promise<string> {
     try {
-      const secret = process.env.CRYPTO_PRIVATE_KEY;
-      if (!secret) {
-        this.logger.error('CRYPTO_PRIVATE_KEY is not configured');
-        throw new InternalServerErrorException('Encryption key is not configured');
-      }
-      const encryptedToken = CryptoJS.AES.encrypt(JSON.stringify(token), secret).toString();
-
-      return encryptedToken;
+      // Use bcrypt to hash the token with a generated salt (cost factor 12 is a good default)
+      const saltRounds = 12;
+      const hashedToken = await bcrypt.hash(token, saltRounds);
+      return hashedToken;
     } catch (error) {
       throw error;
     }
EOF
@@ -18,6 +18,7 @@
import { ClientProxy, RpcException } from '@nestjs/microservices';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as bcrypt from 'bcrypt';
import { map } from 'rxjs/operators';
dotenv.config();
import {
@@ -2201,14 +2202,10 @@

private async tokenEncryption(token: string): Promise<string> {
try {
const secret = process.env.CRYPTO_PRIVATE_KEY;
if (!secret) {
this.logger.error('CRYPTO_PRIVATE_KEY is not configured');
throw new InternalServerErrorException('Encryption key is not configured');
}
const encryptedToken = CryptoJS.AES.encrypt(JSON.stringify(token), secret).toString();

return encryptedToken;
// Use bcrypt to hash the token with a generated salt (cost factor 12 is a good default)
const saltRounds = 12;
const hashedToken = await bcrypt.hash(token, saltRounds);
return hashedToken;
} catch (error) {
throw error;
}
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/webhook/src/webhook.service.ts (2)

28-54: Type mismatch: returning string instead of interface object.

The method signature declares Promise<ICreateWebhookUrl>, but Line 47 returns webhookUrl.webhookUrl, which is a string property. The repository's registerWebhook returns the full org_agents object (result of Prisma's update), so this should return the entire object, not just the string field.

Apply this diff:

-        return webhookUrl.webhookUrl;
+        return webhookUrl;

Alternatively, if ICreateWebhookUrl is intended to be a string type alias, update the return statement to match that contract. Verify the interface definition to confirm the correct behavior.


56-70: Type mismatch: returning string instead of interface object.

The method signature declares Promise<IGetWebhookUrl>, but Line 65 returns webhookUrlInfo.webhookUrl, which is a string property. The repository's getWebhookUrl returns the full org_agents object from Prisma, so this should return the entire object, not just the string field.

Apply this diff:

-      return webhookUrlInfo.webhookUrl;
+      return webhookUrlInfo;

Alternatively, if IGetWebhookUrl is intended to be a string type alias, update the return statement to match that contract. Verify the interface definition to confirm the correct behavior.

apps/cloud-wallet/src/cloud-wallet.service.ts (2)

146-159: Fix template literal syntax error.

Line 151 contains a syntax error with an extra closing brace in the template literal, which will result in a malformed URL and cause API calls to fail.

Apply this diff:

-      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}}`;
+      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}`;

166-180: Fix template literal syntax error.

Line 173 contains a syntax error with an extra closing brace in the template literal, which will result in a malformed URL and cause API calls to fail.

Apply this diff:

-      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${threadParam}}`;
+      const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${threadParam}`;
apps/agent-service/src/agent-service.service.ts (1)

1930-1940: Bug: if (OrgAgentType.SHARED) is always true in deleteWallet

In deleteWallet:

let getApiKey;
if (OrgAgentType.SHARED) {
  const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent(...);
  getApiKey = await this.commonService.decryptPassword(platformAdminSpinnedUp?.org_agents[0].apiKey);
} else {
  getApiKey = getApiKeyResult?.value;
}

OrgAgentType.SHARED is an enum value, so this condition is always truthy. You never use the org-specific API key path, even for dedicated agents.

This can break deletion for dedicated wallets (wrong key) and hides config problems.

You likely meant to branch on orgAgentTypeResult.agent (or equivalent):

-      let getApiKey;
-      if (OrgAgentType.SHARED) {
+      let getApiKey;
+      if (orgAgentTypeResult.agent === OrgAgentType.SHARED) {
         const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent(
           CommonConstants.PLATFORM_ADMIN_ORG
         );
         getApiKey = await this.commonService.decryptPassword(platformAdminSpinnedUp?.org_agents[0].apiKey);
       } else {
         getApiKey = getApiKeyResult?.value;
       }

This is a functional bug and should be corrected before release.

Comment on lines +52 to +57
export function dateToSeconds(date: Date | DateOnly): number {
const realDate = date instanceof DateOnly ? new Date(date.toISOString()) : date;
if (isNaN(realDate.getTime())) {
throw new TypeError('dateToSeconds: invalid date');
}
return Math.floor(realDate.getTime() / 1000);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

dateToSeconds has environment‑dependent behavior for DateOnly inputs

For DateOnly values, this function does:

const realDate = date instanceof DateOnly ? new Date(date.toISOString()) : date;

Given date.toISOString() returns YYYY-MM-DD, parsing this string is implementation‑ and environment‑dependent (some engines treat it as local time, some as UTC), so:

  • The same DateOnly can produce different epoch seconds on different runtimes or host timezones.
  • Any original timezone intent that went into constructing DateOnly is fully lost in a non‑obvious way.
  • Callers may assume “midnight UTC” but actually get “midnight local” (or vice versa).

To make this deterministic:

  • Decide whether DateOnly represents a UTC date or a local calendar date.
  • Then derive seconds accordingly, e.g. for UTC semantics:
export function dateToSeconds(date: Date | DateOnly): number {
  const d =
    date instanceof DateOnly
      ? new Date(Date.UTC(
          // Get the date parts from the string or from internal Date
          Number(date.toISOString().slice(0, 4)),
          Number(date.toISOString().slice(5, 7)) - 1,
          Number(date.toISOString().slice(8, 10)),
        ))
      : date;

  if (isNaN(d.getTime())) {
    throw new TypeError('dateToSeconds: invalid date');
  }

  return Math.floor(d.getTime() / 1000);
}

Or, better, expose a toDate()/toEpochSeconds() method on DateOnly and delegate to that so the normalization logic lives in one place.

🤖 Prompt for AI Agents
In libs/common/src/date-only.ts around lines 52-57, dateToSeconds currently
builds a Date from date.toISOString(), which parses "YYYY-MM-DD" in an
environment-dependent way; make it deterministic by treating DateOnly as a
calendar date in UTC and constructing the epoch seconds from the ISO date parts
(year, month-1, day) using UTC arithmetic, or preferably call a new/existing
DateOnly.toDate() or toEpochSeconds() method that encapsulates this logic;
ensure invalid dates still throw the same TypeError and return
Math.floor(epochMillis/1000).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (2)

298-308: Decouple requestSigner from Prisma client types.

The DTO imports SignerOption from @prisma/client (line 17) and uses it as the type for requestSigner.method (line 307). This couples the API contract to the persistence layer, which was flagged in the previous review.

There's a separate SignerOption interface in apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts with a different shape ({ method: SignerMethodOption; did?: string; x5c?: string[] }), creating naming ambiguity.

Recommendation: Replace the Prisma import with a domain-level DTO or enum from a shared interface module:

-import { SignerOption } from '@prisma/client';
+import { SignerMethodOption } from '@credebl/common/interfaces/oid4vc'; // or appropriate path

Then update the type:

 requestSigner?: {
-  method: SignerOption;
+  method: SignerMethodOption;
 };

This keeps the API surface independent of database schema changes.

Based on learnings


341-347: Remove contradictory @ApiPropertyOptional decorator.

Lines 341–344 apply both @ApiHideProperty() and @ApiPropertyOptional() to _oneOfCheck. These decorators contradict each other:

  • @ApiHideProperty() excludes the field from the OpenAPI schema.
  • @ApiPropertyOptional() includes it.

Since the intent (per the comment) is to keep this field internal to validation, remove the @ApiPropertyOptional decorator:

 @ApiHideProperty()
-@ApiPropertyOptional({
-  description: 'Internal: ensures exactly one of dcql or presentationExchange is present'
-})
 @Validate(OnlyOneOfConstraint, ['dcql', 'presentationExchange'])
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 _oneOfCheck?: any;

Based on learnings

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (3)

141-198: Re-verify issuer service method names (oidcGetIssuerById / oidcGetIssuers)

The controller calls:

const oidcIssuer = await this.oid4vcIssuanceService.oidcGetIssuerById(id, orgId);
...
const oidcIssuer = await this.oid4vcIssuanceService.oidcGetIssuers(orgId);

A previous review noted that the service defined oidcIssuerGetById and oidcIssuers instead. If the service names were not updated to match the controller, this will fail TypeScript compilation.

Please confirm that Oid4vcIssuanceService actually exposes oidcGetIssuerById(id, orgId) and oidcGetIssuers(orgId) (or adjust one side so they match consistently).

You can verify alignment with a quick search:

#!/bin/bash
# From repo root
service_file=$(fd oid4vc-issuance.service.ts | head -n 1)
echo "Service file: $service_file"

rg -n "oidcIssuerGetById\\(|oidcGetIssuerById\\(" "$service_file" -n -C2 || echo "No issuer get-by-id methods found"
rg -n "oidcIssuers\\(|oidcGetIssuers\\(" "$service_file" -n -C2 || echo "No issuer list methods found"

240-438: Template endpoints: double-check service signatures and issuer scoping

There are a couple of points worth tightening up in the template handlers:

  1. Service call signatures vs service implementation

    • Create template:

      const template = await this.oid4vcIssuanceService.createTemplate(
        CredentialTemplate,
        user,
        orgId,
        issuerId
      );
    • Update template:

      const updated = await this.oid4vcIssuanceService.updateTemplate(
        user,
        orgId,
        templateId,
        dto,
        issuerId
      );

    A previous review indicated the service originally exposed different parameter orders (e.g., (credentialTemplate, orgId, issuerId) for create, (templateId, dto, orgId, issuerId) for update). If the service signatures were not updated to match these controller calls, you’ll hit compile-time errors or, worse, subtle bugs if any of the args are structurally compatible.

    Please re-check Oid4vcIssuanceService.createTemplate / .updateTemplate signatures and align the argument order on one side only (controller or service), and keep that ordering consistent across all template flows.

  2. issuerId is parsed but not forwarded on delete
    In deleteTemplate you validate issuerId via ParseUUIDPipe, but the service is called without it:

    await this.oid4vcIssuanceService.deleteTemplate(user, orgId, templateId);

    That means issuer scoping for deletions cannot rely on the path parameter. If templates are meant to be issuer‑scoped (as suggested by the route and descriptions), it’s safer to forward issuerId into the service and validate that the template belongs to that issuer.

    For example (controller side only):

  • await this.oid4vcIssuanceService.deleteTemplate(user, orgId, templateId);
  • await this.oid4vcIssuanceService.deleteTemplate(user, orgId, templateId, issuerId);

and then extend the service signature accordingly.

---

`440-588`: **Fix credential-offer delete response status and re-check “get all offers” service name**

1. **204 with JSON body & Swagger mismatch (delete offer)**  
`deleteCredentialOffers` currently builds a body but returns 204:

```ts
const finalResponse: IResponse = {
  statusCode: HttpStatus.NO_CONTENT,
  message: ResponseMessages.oidcIssuerSession.success.delete,
  data: deletedofferDetails
};
return res.status(HttpStatus.NO_CONTENT).json(finalResponse);
  • HTTP 204 semantics imply no response body.
  • @ApiResponse documents HttpStatus.OK.

This combination is inconsistent and can confuse clients and tooling. Easiest fix is to return 200 with the body, matching the Swagger annotation:

-    const finalResponse: IResponse = {
-      statusCode: HttpStatus.NO_CONTENT,
+    const finalResponse: IResponse = {
+      statusCode: HttpStatus.OK,
    message: ResponseMessages.oidcIssuerSession.success.delete,
    data: deletedofferDetails
  };
-
-    return res.status(HttpStatus.NO_CONTENT).json(finalResponse);
+    return res.status(HttpStatus.OK).json(finalResponse);
  1. getAllCredentialOffers vs service method name
    The controller calls:

    const connectionDetails = await this.oid4vcIssuanceService.getAllCredentialOffers(
      orgId,
      getAllCredentialOffer
    );

    A previous review noted the service method was named getCredentialOffers(orgId, dto). If that name remains, this will not compile.

    Please ensure the service now exposes getAllCredentialOffers or, if not, rename the call (or method) so both sides agree.

You can quickly confirm the service method name with:

#!/bin/bash
# From repo root
service_file=$(fd oid4vc-issuance.service.ts | head -n 1)
echo "Service file: $service_file"

rg -n "getAllCredentialOffers\\(" "$service_file" -n -C2 || echo "getAllCredentialOffers not found"
rg -n "getCredentialOffers\\(" "$service_file" -n -C2 || echo "getCredentialOffers not found"
🧹 Nitpick comments (4)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (3)

137-160: Validate or document the format field structure.

The format field (line 153) uses an unvalidated Record<string, unknown>, which permits arbitrary key-value pairs. Per PEX spec, format identifiers typically follow a known set (e.g., jwt_vc, jwt_vp, ldp_vc, vc+sd-jwt). Consider either:

  • Creating a dedicated DTO class with specific format keys and @ValidateNested(), or
  • Documenting the expected structure in the @ApiProperty description if validation is intentionally deferred to downstream services.

214-243: Validate or document the meta field structure.

The meta field (line 227) uses an unvalidated Record<string, unknown>. Similar to the PEX format field, this allows arbitrary key-value pairs. For DCQL queries, meta typically contains credential-type metadata (e.g., vct for VC types). Consider creating a dedicated DTO or documenting the expected structure to prevent runtime surprises.


276-296: Consider conventional comparison order.

Line 285 uses null !== object[p] instead of the more conventional object[p] !== null. While functionally equivalent, the conventional order improves readability and consistency with typical JavaScript/TypeScript style.

-      if (object[p] !== undefined && null !== object[p]) {
+      if (object[p] !== undefined && object[p] !== null) {
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (1)

74-139: Align issuer create/update HTTP status codes and docs

oidcIssuerCreate and oidcIssuerUpdate both respond with HttpStatus.CREATED (201) in finalResponse.statusCode and res.status(...), but the @ApiResponse decorators document HttpStatus.OK (200). That drift will confuse clients relying on the OpenAPI.

Consider:

  • For create: keep 201 but change @ApiResponse({ status: HttpStatus.CREATED, ... }).
  • For update: either switch to 200 in code and docs, or explicitly document 201 if you want “upsert”-like semantics.

Also, the JSDoc above oidcIssuerCreate still talks about “status of the deletion operation”; it should describe issuer creation for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0788698 and 07e0866.

📒 Files selected for processing (3)
  • Dockerfiles/Dockerfile.x509 (1 hunks)
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (1 hunks)
  • apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Dockerfiles/Dockerfile.x509
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (2)
libs/common/src/cast.helper.ts (2)
  • ValidatorConstraint (129-165)
  • ValidatorConstraint (382-397)
apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts (1)
  • SignerOption (16-20)
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (6)
libs/common/src/response-messages/index.ts (1)
  • ResponseMessages (1-639)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts (2)
  • IssuerCreationDto (84-154)
  • IssuerUpdationDto (156-180)
libs/common/src/interfaces/response.interface.ts (2)
  • IResponse (8-13)
  • IResponseType (1-7)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts (1)
  • UpdateCredentialTemplateDto (288-288)
apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts (4)
  • CreateOidcCredentialOfferDto (169-190)
  • UpdateCredentialRequestDto (210-223)
  • GetAllCredentialOfferDto (192-208)
  • CreateCredentialOfferD2ADto (303-389)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)
  • OidcIssueCredentialDto (21-65)
🔇 Additional comments (1)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1)

23-72: LGTM: Query parameter validation is appropriate.

The VerificationPresentationQueryDto properly validates optional query parameters with appropriate decorators. The use of @IsUrl for authorizationRequestUri and @IsEnum for the state field ensures type safety.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (22)
libs/common/src/response-messages/index.ts (2)

530-530: Literal ${templateId} won't be interpolated.

The string uses ${templateId} inside single quotes, so it will render literally instead of being interpolated. This is inconsistent with failedDeleteTemplate on line 542, which expects the ID to be appended externally.

As per the previous review, either remove the placeholder:

-      deleteTemplate: '[createTemplate] compensating delete succeeded for templateId=${templateId}'
+      deleteTemplate: '[createTemplate] compensating delete succeeded for templateId='

Or convert to a function that accepts templateId at call time.


593-593: Incorrect terminology: "template" in session context.

The deleteTemplate key and message reference "template" within the oid4vpSession error section, which handles session operations, not templates.

Update to session-appropriate terminology:

-      deleteTemplate: 'Error while deleting template.'
+      deleteSession: 'Error while deleting session.'

Or if more specificity is needed:

-      deleteTemplate: 'Error while deleting template.'
+      deleteVerifierSession: 'Error while deleting verifier session.'
apps/oid4vc-verification/src/oid4vc-verification.service.ts (6)

64-78: Fix undefined dereference in conflict error message.

When the agent returns a 409 status before createdVerifierDetails is assigned, line 75 will throw a TypeError attempting to read .verifierId from undefined. Use the verifierId from the request parameter instead.

Apply this diff:

       } catch (error) {
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;
 
         if (status409) {
-          throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
+          throw new ConflictException(`Verifier with id '${verifierId}' already exists`);
         }
         throw error;
       }

108-140: Fix impossible array guard and undefined dereference in conflict handler.

Line 109's condition 0 > existingVerifiers.length checks for negative length, which is impossible. Additionally, getVerifiersByVerifierId returns Promise<oid4vp_verifier[] | null>, so line 109 will throw if null. Line 137 dereferences updatedVerifierDetails.verifierId when it may be undefined if the agent rejects the update.

Apply this diff:

       let updatedVerifierDetails;
       const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 > existingVerifiers.length) {
+      if (!existingVerifiers || existingVerifiers.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
       // updateVerifier['verifierId'] = existingVerifiers[0].publicVerifierId
@@ -133,7 +133,8 @@
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;
 
         if (status409) {
-          throw new ConflictException(`Verifier with id '${updatedVerifierDetails.verifierId}' already exists`);
+          const conflictId = existingVerifiers?.[0]?.publicVerifierId ?? verifierId;
+          throw new ConflictException(`Verifier with id '${conflictId}' already exists`);
         }
         throw error;
       }

175-205: Add null guard and use agent-facing ID.

Line 179 will throw if checkIdExist is null (repository returns oid4vp_verifier[] | null). Line 188 passes the internal verifierId to the agent, but other methods (e.g., line 121 in oid4vpUpdateVerifier) correctly use publicVerifierId for agent communication.

Apply this diff:

       const checkIdExist = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 == checkIdExist.length) {
+      if (!checkIdExist || checkIdExist.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
 
@@ -185,7 +185,7 @@
         throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
       }
       const { agentEndPoint, id } = agentDetails;
-      const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_DELETE, checkIdExist[0].verifierId);
+      const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_DELETE, checkIdExist[0].publicVerifierId);
       this.logger.debug(`[deleteVerifierById] calling agent URL=${url}`);

234-244: Fix typo: methodv should be method.

Line 241 passes sessionRequest.requestSigner.methodv (undefined) instead of sessionRequest.requestSigner.method. This breaks X.509 certificate lookups, causing every x5c-based flow to fail with "No active certificate found".

Apply this diff:

         const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
           orgId,
-          sessionRequest.requestSigner.methodv
+          sessionRequest.requestSigner.method
         );

304-304: Remove redundant await.

Line 304 has await await, which is redundant and indicates a copy-paste error.

Apply this diff:

-      const verifiers = await await this._getOid4vpVerifierSession(url, orgId);
+      const verifiers = await this._getOid4vpVerifierSession(url, orgId);

331-331: Remove redundant await.

Line 331 has the same double await issue as line 304.

Apply this diff:

-      const verifiers = await await this._getOid4vpVerifierSession(url, orgId);
+      const verifiers = await this._getOid4vpVerifierSession(url, orgId);
libs/logger/src/logging.interceptor.ts (1)

24-33: Add explicit string type validation before using contextId from headers.

As flagged in previous reviews, the current truthiness check doesn't enforce that contextId is a string. If headers.get('contextId') returns a non-string truthy value (e.g., 123, { id: 'abc' }, true), it will bypass validation and get passed to setupContextId, potentially causing type violations downstream.

While you expect contextId to be a string or empty, the code doesn't enforce this expectation, making it vulnerable to incorrect upstream data.

Apply this diff to add explicit type validation:

-      const contextIdFromHeader = headers && 'function' === typeof headers.get ? headers.get('contextId') : undefined;
+      const rawContextId = headers && typeof headers.get === 'function' ? headers.get('contextId') : undefined;
+      const contextIdFromHeader = typeof rawContextId === 'string' && rawContextId ? rawContextId : undefined;

This ensures that:

  • Only non-empty strings are accepted as valid context IDs
  • Any other type (number, object, boolean) triggers UUID generation
  • Empty strings are treated as missing and trigger UUID generation
  • The type contract of setupContextId(contextIdFromHeader: string) is never violated

Based on past review comments.

apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (4)

61-67: JSDoc and method name still describe an issuer while implementing a verifier create

The block comment and method name oidcIssuerCreate are misleading for an endpoint that clearly creates an OID4VP verifier (/orgs/:orgId/oid4vp/verifier, ResponseMessages.oid4vp.*). This makes the code harder to follow and can confuse future maintainers.

Consider at least fixing the JSDoc, and ideally renaming the method to something verifier-specific (e.g. createVerifier / oid4vpCreateVerifier) to align with the route and service:

-  /**
-   * Create issuer against a org(tenant)
-   * @param orgId The ID of the organization
-   * @param user The user making the request
-   * @param res The response object
-   * @returns The status of the deletion operation
-   */
+  /**
+   * Create OID4VP verifier for an organization
+   * @param orgId The ID of the organization (tenant)
+   * @param user The user making the request
+   * @param createVerifier The verifier creation payload
+   * @param res The response object
+   * @returns The created verifier details
+   */

Also applies to: 78-104


308-352: Response envelope for presentation GETs diverges from IResponse/ApiResponseDto contract

getVerificationPresentation and getVerificationPresentationResponse currently return a custom object:

return res.status(HttpStatus.OK).json({
  success: true,
  message: '...',
  data: result
});

Every other route in this controller (and the OID4VC issuance controller) returns an IResponse/IResponseType with statusCode, message from ResponseMessages.*, and optional data, matching ApiResponseDto.

For consistency and to avoid surprising consumers:

  • Construct an IResponse object with statusCode: HttpStatus.OK, a suitable ResponseMessages.oid4vpSession.success.* key, and data: result.
  • Return it via res.status(HttpStatus.OK).json(finalResponse) like the other methods.

You could also rely on CustomExceptionFilter instead of the manual try/catch + BadRequestException if you don’t need to normalize all errors to 400.

Also applies to: 354-413


106-113: Update verifier JSDoc is incorrect and response statusCode doesn’t match HTTP status

Two issues here:

  1. The JSDoc still talks about “Create issuer” even though this is an OID4VP verifier update, and omits the updateVerifier payload parameter.
  2. @ApiResponse and res.status use HttpStatus.OK, but finalResponse.statusCode is HttpStatus.CREATED. That gives clients conflicting information between the HTTP status line and the JSON body.

Suggested fix:

-  /**
-   * Create issuer against a org(tenant)
-   * @param orgId The ID of the organization
-   * @param verifierId The ID of the Verifier
-   * @param user The user making the request
-   * @param res The response object
-   * @returns The status of the verifier update operation
-   */
+  /**
+   * Update OID4VP verifier for an organization
+   * @param orgId The ID of the organization (tenant)
+   * @param verifierId The ID of the verifier
+   * @param user The user making the request
+   * @param updateVerifier The verifier update payload
+   * @param res The response object
+   * @returns The updated verifier details
+   */
@@
-    const finalResponse: IResponse = {
-      statusCode: HttpStatus.CREATED,
+    const finalResponse: IResponse = {
+      statusCode: HttpStatus.OK,
       message: ResponseMessages.oid4vp.success.update,
       data: updateVerifierRes
     };

(As above, also consider renaming oidcIssuerUpdate to something verifier-specific.)

Also applies to: 114-155


157-194: verifierId UUID validation conflicts with DTO example and public verifier identifier usage

Across multiple endpoints, verifierId is validated as a UUID v4:

  • GET /orgs/:orgId/oid4vp/verifier (@Query('verifierId', new ParseUUIDPipe({ version: '4', optional: true, ... })))
  • DELETE /orgs/:orgId/oid4vp/verifier (@Query('verifierId', new ParseUUIDPipe(...)))
  • POST /orgs/:orgId/oid4vp/presentation (@Query('verifierId', new ParseUUIDPipe(...)))

However, CreateVerifierDto documents verifierId as an arbitrary string with example 'verifier-12345', and VerificationPresentationQueryDto introduces a separate publicVerifierId field, suggesting a public, non-UUID identifier.

As written, any client following the DTO example ('verifier-12345') will get a 400 due to UUID validation, and it’s unclear whether these routes expect the internal UUID (id) or the public identifier.

You should reconcile this contract:

  • If API callers are meant to use the public verifier ID (which matches the DTO example):

    • Drop ParseUUIDPipe for verifierId (or rename the parameter to publicVerifierId) and treat it as string.
    • Update @ApiQuery/Swagger descriptions to say “Public identifier of the verifier (e.g. verifier-12345)”.
    • Ensure oid4vcVerificationService queries by the public field rather than the internal UUID.
  • If the endpoints are intended to use the internal UUID:

    • Update CreateVerifierDto to document a UUID example and clarify that verifierId is a UUID.
    • Consider introducing a separate publicVerifierId in the DTOs if a human-friendly ID is still needed, and adjust service/database layers accordingly.
#!/bin/bash
# Quick sanity check: how verifier identifiers are modeled and queried
rg -n "publicVerifierId" apps -C3 || true
rg -n "getVerifierById" apps -C5 || true
rg -n "verifierId" apps/oid4vc-verification apps/api-gateway/src/oid4vc-verification -C3

Also applies to: 208-236, 275-282

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (2)

618-666: Webhook handler wiring looks correct; logging is scoped and avoids raw console usage

getIssueCredentialWebhook:

  • Sets orgId onto the DTO when contextCorrelationId === 'default' and id is present.
  • Delegates to the service for core processing.
  • Resolves a webhook URL via _getWebhookUrl and, if present, posts back using _postWebhookResponse, with errors logged via the Nest logger rather than console.log.

Given your earlier note about not wanting to apply the sanitizer yet, this is a reasonable balance between functionality and observability.


566-588: Don’t return a JSON body with HTTP 204 in deleteCredentialOffers

The delete-offer endpoint currently does:

const deletedofferDetails = await this.oid4vcIssuanceService.deleteCredentialOffers(orgId, credentialId);

const finalResponse: IResponse = {
  statusCode: HttpStatus.NO_CONTENT,
  message: ResponseMessages.oidcIssuerSession.success.delete,
  data: deletedofferDetails
};
return res.status(HttpStatus.NO_CONTENT).json(finalResponse);

HTTP 204 expressly means “no content”; clients typically won’t expect or process a body for that status. Either:

  • Switch to HttpStatus.OK (or 202) if you want to return details, or
  • Keep 204 and omit the body entirely.

I’d lean toward 200/OK here, given you already have a useful response payload.

apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (2)

42-51: Align CredentialConfig.claims type with the nested claims object actually produced

CredentialConfig.claims is declared as Claim[], but:

  • buildNestedClaims builds a nested Record<string, Claim> structure, and even merges child claim maps into each Claim node.
  • buildClaimsFromTemplate returns Record<string, any>.
  • Both buildSdJwtCredentialConfig and buildMdocCredentialConfig assign that object directly to claims.

So at runtime claims is a nested object keyed by attribute/namespace, not a Claim[]. This type/shape drift makes downstream use error‑prone and defeats TS guarantees.

Consider either:

  • Updating the type to match the produced shape, e.g.:
type CredentialConfig = {
  // ...
- claims: Claim[];
+ claims: Record<string, Claim | Record<string, Claim>>;
};

(and tightening buildClaimsFromTemplate’s return type), or

  • Refactoring the builders to emit a Claim[] (or a well-typed tree) if that’s the intended consumer contract.

Right now types and values disagree in a core helper.

Also applies to: 243-301, 305-357


391-405: Guard appearanceJson before accessing .display to avoid null/primitive crashes

coerceJsonObject can return null or a non-object primitive, but displayConfigurations does:

const appearanceJson = coerceJsonObject<unknown>(templateRow.appearance);

const displayConfigurations =
  (appearanceJson as Appearance).display?.map(/* ... */) ?? [];

If appearanceJson is null/non-object, the cast and .display access will throw before ?.map runs.

Wrap with a type guard:

const appearanceJson = coerceJsonObject<unknown>(templateRow.appearance);
const appearance =
  appearanceJson && typeof appearanceJson === 'object'
    ? (appearanceJson as Appearance)
    : null;

const displayConfigurations =
  appearance?.display?.map((displayEntry) => ({
    // ...
  })) ?? [];

This keeps issuer metadata building robust to bad/empty appearance JSON.

libs/common/src/common.constant.ts (1)

120-143: Resolve TODO on URL_OIDC_VERIFIER_CREATE (and verify OIDC/OID4VP URL constants)

URL_OIDC_VERIFIER_CREATE is still:

URL_OIDC_VERIFIER_CREATE = '/openid4vc/verifier', //TODO: correct this URL

and several other OIDC4VC/OID4VP URLs were newly added here. Because these constants are wired into getAgentUrl and agent-service calls, leaving an explicitly “TODO: correct this URL” in production code will surface as opaque runtime failures when those paths are exercised.

Before enabling this feature, either:

  • Update this and related verifier URLs to the final, correct paths and remove the TODO, or
  • Gate their usage behind a feature flag and track the URL correction in an issue so it doesn’t get forgotten.

Right now the comment advertises that at least one URL is known wrong.

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (3)

200-212: Fix null‑check in oidcIssuerGetById to avoid crash when issuer is missing

Currently:

const getIssuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(id);
if (!getIssuerDetails && !getIssuerDetails.publicIssuerId) {
  throw new NotFoundException(...);
}

If getIssuerDetails is null/undefined, !getIssuerDetails is true and the right-hand side of && still evaluates, causing Cannot read properties of undefined (reading 'publicIssuerId').

Change this to an OR with optional chaining:

if (!getIssuerDetails || !getIssuerDetails?.publicIssuerId) {
  throw new NotFoundException(ResponseMessages.oidcIssuer.error.notFound);
}

so you never dereference publicIssuerId on a missing issuer.


265-292: Use RpcException instead of throwing a raw Error in deleteOidcIssuer

In the deleteOidcIssuer catch block you special-case Prisma errors, but for everything else you:

if (error.name === 'PrismaClientKnownRequestError') { ... }
throw new Error(error.response ? error.response : error);

Throwing a plain Error here is inconsistent with the rest of the service (which uses RpcException) and will lose structured status/message information for consumers.

Prefer:

} catch (error) {
  if (error?.name === 'PrismaClientKnownRequestError') {
    throw new BadRequestException(error.meta?.cause ?? ResponseMessages.oidcIssuer.error.deleteFailed);
  }
  this.logger.error(`[deleteOidcIssuer] - error: ${JSON.stringify(error)}`);
  throw new RpcException(error?.response ?? error);
}

to preserve RPC semantics and logging.


627-649: Remove hard‑coded 'Test' x5c stub in createOidcCredentialOfferD2A

This path still uses:

if (signerOptions.method === SignerMethodOption.X5C) {
  if (!signerOptions.x5c || 0 === signerOptions.x5c.length) {
    const x5cFromDb = 'Test';
    // ...
    signerOptions.x5c = x5cFromDb;
  }
}

This introduces two problems:

  • A dummy 'Test' value will produce invalid credential offers if this path is hit.
  • signerOptions.x5c is typed as string[], but you assign a bare string.

You should either:

  • Implement the real DB lookup (e.g., await this.oid4vcIssuanceRepository.getIssuerX5c(oidcCredentialD2APayload.publicIssuerId, orgId)), assigning the returned array to signerOptions.x5c, and error if it’s empty, or
  • Fail fast with a BadRequestException when x5c is missing until the DB integration is ready, instead of injecting a fake value.

Leaving this stub in will break any real X.509 flows using the D2A endpoint.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)

34-37: Enable nested validation for credentialOfferPayload so inner array constraints run

Right now:

@ApiProperty({ type: CredentialOfferPayloadDto })
@IsObject()
credentialOfferPayload!: CredentialOfferPayloadDto;

Without @ValidateNested() and a @Type hint, class-validator treats credentialOfferPayload as an opaque object, so the @IsArray() on CredentialOfferPayloadDto.credential_configuration_ids is never enforced. A malformed webhook payload (e.g. scalar instead of array) will slip through and extractCredentialConfigurationIds will just return [], hiding the error.

Wire up nested validation:

import { ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';

@ApiProperty({ type: CredentialOfferPayloadDto })
@IsObject()
@ValidateNested()
@Type(() => CredentialOfferPayloadDto)
credentialOfferPayload!: CredentialOfferPayloadDto;

so invalid inner shapes are rejected instead of silently ignored.

Also applies to: 70-73

🧹 Nitpick comments (10)
apps/oid4vc-verification/test/app.e2e-spec.ts (1)

8-15: Add cleanup in afterEach when implementing tests.

The app is initialized in beforeEach but there's no corresponding cleanup. When you add actual test cases, ensure proper resource cleanup to prevent memory leaks and port conflicts.

Add this after the beforeEach block:

+  afterEach(async () => {
+    await app.close();
+  });
libs/common/src/response-messages/index.ts (1)

557-557: Add space after "Info" for better readability.

Minor formatting improvement: add a space between "Info" and the opening parenthesis.

-      missingValidityInfo: 'Validity Info(validFrom, validTo) is required for validity of credential'
+      missingValidityInfo: 'Validity Info (validFrom, validTo) is required for validity of credential'
apps/oid4vc-verification/src/oid4vc-verification.service.ts (1)

1-8: Consider progressively re-enabling ESLint rules.

Disabling many linting rules (camelcase, no-console, no-unused-vars, no-explicit-any, etc.) increases technical debt. Since this is marked experimental, consider creating follow-up issues to address violations and re-enable rules incrementally as the code matures.

apps/api-gateway/src/x509/dtos/x509.dto.ts (2)

25-25: Remove commented code.

Multiple locations contain commented-out code that should be removed to improve maintainability:

  • Line 25: Commented default value
  • Line 276: Commented default value
  • Lines 295-296: Commented optional fields

Apply this diff:

  @ApiProperty({
    enum: x5cKeyType,
-    //default: x5cKeyType.P256.toString(),
    description: 'Type of the key used for signing the X.509 Certificate (default is p256)'
  })
  @ApiProperty({
    enum: x5cKeyType,
-    //default: x5cKeyType.P256.toString(),
    description: 'Type of the key used for signing the X.509 Certificate (default is p256)'
  })
export class X509CertificateSubjectOptionsDto {
  @ApiProperty() @IsNotEmpty() @IsString() countryName: string;
-  // @ApiPropertyOptional() @IsOptional() @IsString() stateOrProvinceName?: string;
-  // @ApiPropertyOptional() @IsOptional() @IsString() organizationalUnit?: string;
  @ApiProperty() @IsNotEmpty() @IsString() commonName: string;
}

Also applies to: 276-276, 295-296


284-291: Use PascalCase for class names.

The classes x509Input and x509OptionsDto use camelCase, which is inconsistent with TypeScript conventions and other DTOs in this file (e.g., NameDto, KeyUsageDto). Class names should use PascalCase.

Apply this diff:

-export class x509Input {
+export class X509Input {
  @ApiProperty({
    description: 'certificate',
    required: true
  })
  @IsString()
  certificate: string;
}
-export class x509OptionsDto {
+export class X509OptionsDto {
  @ApiProperty({ example: 'exampleOrg' })
  @IsNotEmpty()
  @IsString()
  commonName: string;

Note: Update any imports/usages of these classes in other files.

Also applies to: 324-334

apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (1)

280-283: Tighten typings to reduce any and improve safety

Two quick wins:

  • buildClaimsFromTemplate currently returns Record<string, any>; you can make this more precise (e.g. Record<string, Claim | Record<string, Claim>>) based on buildNestedClaims rather than falling back to any.
  • buildCredentialConfigurationsSupported takes templateRows: any even though you already have a TemplateRowPrisma shape; using TemplateRowPrisma[] (or the generated Prisma type) will catch mismatches between DB schema and builder logic.

These changes would make changes to template/appearance structures safer and satisfy static-analysis concerns without altering behavior.

Also applies to: 378-417

apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (1)

242-268: Type certificate parameter for date‑window validation

validateCredentialDatesInCertificateWindow leaves certificate untyped:

function validateCredentialDatesInCertificateWindow(credentialValidityInfo: validityInfo, certificate) { ... }

Since callers always pass an X509CertificateRecord, giving this parameter that type (or at least { validFrom: Date; expiry: Date; certificateBase64: string }) will improve readability and catch misuse at compile time with no runtime impact.

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (2)

740-761: Avoid returning HTTP 204 semantics with a non‑empty body from microservice helpers

deleteCredentialOffers builds a response where the controller ultimately sends:

  • statusCode: HttpStatus.NO_CONTENT and
  • a JSON body containing data: deletedofferDetails.

While the actual HTTP status is set by the controller, having service‑level semantics that imply “no content” but still include a body can cause confusion for upstream code and API docs. Prefer to treat a successful delete as 200/202 with a payload, and reserve 204 for “no response body expected”.

Consider standardizing this at the controller level (see comment in the controller file) and keeping service methods agnostic about specific HTTP codes.


963-1007: Clean up unused locals in storeOidcCredentialWebhook or use them explicitly

Inside storeOidcCredentialWebhook you compute:

  • contextCorrelationId, credentialOfferPayload, issuedCredentials, issuanceSessionId via destructuring.
  • cfgIds, issuedCredentialsArr.
  • sanitized.

But only CredentialOfferWebhookPayload and organisationId are passed to storeOidcCredentialDetails. The intermediate derived structures aren’t used, and static analysis already flags some of these assignments as useless.

Either:

  • Use cfgIds/issuedCredentialsArr/sanitized in what you persist, or
  • Remove the dead code to keep the function focused and quiet static analysis.

Right now this logic is misleading and suggests partial sanitization that doesn’t actually happen.

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (1)

240-268: Template routes and service calls now align; consider adding orgId ParseUUIDPipe for symmetry

The create/update/delete template routes now correctly forward (CredentialTemplate, user, orgId, issuerId) / (user, orgId, templateId, dto, issuerId) / (user, orgId, templateId, issuerId) to the service, which matches the service signatures and resolves prior mismatches.

Optionally, for consistency with other endpoints, you could also apply ParseUUIDPipe to orgId and issuerId on the create route, but that’s a nice-to-have rather than a blocker.

Also applies to: 349-381, 392-438

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07e0866 and c72b01e.

📒 Files selected for processing (15)
  • apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1 hunks)
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (1 hunks)
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts (1 hunks)
  • apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (1 hunks)
  • apps/api-gateway/src/x509/dtos/x509.dto.ts (1 hunks)
  • apps/api-gateway/src/x509/x509.service.ts (1 hunks)
  • apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (1 hunks)
  • apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (1 hunks)
  • apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (1 hunks)
  • apps/oid4vc-verification/src/oid4vc-verification.service.ts (1 hunks)
  • apps/oid4vc-verification/test/app.e2e-spec.ts (1 hunks)
  • libs/common/src/common.constant.ts (3 hunks)
  • libs/common/src/common.utils.ts (3 hunks)
  • libs/common/src/response-messages/index.ts (1 hunks)
  • libs/logger/src/logging.interceptor.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api-gateway/src/x509/x509.service.ts
🧰 Additional context used
🧬 Code graph analysis (9)
libs/logger/src/logging.interceptor.ts (1)
libs/logger/src/logger.interface.ts (2)
  • LoggerKey (4-4)
  • Logger (6-20)
apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (11)
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (1)
  • Controller (58-667)
apps/agent-service/src/agent-service.controller.ts (1)
  • Controller (37-469)
apps/oid4vc-verification/src/oid4vc-verification.controller.ts (1)
  • Controller (9-105)
libs/logger/src/logger.interface.ts (1)
  • Logger (6-20)
apps/api-gateway/src/authz/decorators/roles.decorator.ts (1)
  • Roles (6-6)
libs/common/src/response-messages/index.ts (1)
  • ResponseMessages (1-634)
libs/common/src/interfaces/organization.interface.ts (1)
  • User (26-33)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier.dto.ts (2)
  • CreateVerifierDto (22-42)
  • UpdateVerifierDto (44-44)
libs/common/src/interfaces/response.interface.ts (2)
  • IResponse (8-13)
  • IResponseType (1-7)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (2)
  • PresentationRequestDto (298-348)
  • VerificationPresentationQueryDto (23-72)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts (1)
  • Oid4vpPresentationWhDto (3-39)
apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (4)
apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces.ts (4)
  • Claim (34-38)
  • SdJwtTemplate (3-6)
  • MdocTemplate (8-14)
  • CredentialAttribute (40-47)
apps/oid4vc-issuance/constant/issuance.ts (1)
  • batchCredentialIssuanceDefault (6-6)
apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts (1)
  • CreateOidcCredentialOffer (45-49)
apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts (1)
  • IssuerResponse (138-151)
apps/api-gateway/src/x509/dtos/x509.dto.ts (2)
libs/common/src/interfaces/x509.interface.ts (1)
  • IX509SearchCriteria (217-220)
libs/common/src/cast.helper.ts (2)
  • toNumber (58-76)
  • trim (39-43)
apps/oid4vc-verification/src/oid4vc-verification.service.ts (8)
apps/oid4vc-verification/src/oid4vc-verification.repository.ts (1)
  • Injectable (12-262)
libs/service/base.service.ts (1)
  • BaseService (2-9)
libs/common/src/interfaces/oid4vp-verification.ts (3)
  • CreateVerifier (6-9)
  • VerifierRecord (15-26)
  • UpdateVerifier (11-13)
libs/common/src/common.utils.ts (1)
  • getAgentUrl (70-140)
libs/common/src/interfaces/x509.interface.ts (1)
  • X509CertificateRecord (203-215)
apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts (2)
  • RequestSigner (23-23)
  • Oid4vpPresentationWh (3-11)
apps/oid4vc-verification/interfaces/oid4vp-verifier.interfaces.ts (1)
  • VerificationSessionQuery (20-27)
libs/common/src/cast.helper.ts (1)
  • buildUrlWithQuery (527-542)
apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (4)
apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts (3)
  • DisclosureFrame (30-30)
  • GetAllCredentialOffer (51-57)
  • SignerOption (16-20)
apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces.ts (3)
  • CredentialAttribute (40-47)
  • SdJwtTemplate (3-6)
  • MdocTemplate (8-14)
libs/common/src/interfaces/x509.interface.ts (1)
  • X509CertificateRecord (203-215)
libs/common/src/date-only.ts (1)
  • dateToSeconds (52-58)
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (7)
apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (1)
  • Controller (52-460)
apps/issuance/src/issuance.controller.ts (1)
  • Controller (26-140)
libs/common/src/response-messages/index.ts (1)
  • ResponseMessages (1-634)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts (2)
  • IssuerCreationDto (84-154)
  • IssuerUpdationDto (156-180)
libs/common/src/interfaces/response.interface.ts (2)
  • IResponse (8-13)
  • IResponseType (1-7)
apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts (4)
  • CreateOidcCredentialOfferDto (169-190)
  • UpdateCredentialRequestDto (210-223)
  • GetAllCredentialOfferDto (192-208)
  • CreateCredentialOfferD2ADto (303-389)
apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)
  • OidcIssueCredentialDto (21-65)
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts (3)
apps/api-gateway/src/issuance/issuance.service.ts (1)
  • Injectable (30-272)
apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts (1)
  • Injectable (10-123)
libs/service/base.service.ts (1)
  • BaseService (2-9)
apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (8)
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts (1)
  • deleteTemplate (56-59)
apps/agent-service/src/agent-service.service.ts (1)
  • deleteOidcIssuer (1439-1452)
apps/oid4vc-issuance/src/oid4vc-issuance.repository.ts (3)
  • addOidcIssuerDetails (191-211)
  • deleteOidcIssuer (232-241)
  • deleteTemplate (346-355)
apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts (6)
  • IssuerCreation (54-62)
  • IssuerInitialConfig (64-74)
  • IssuerMetadata (76-82)
  • IssuerUpdation (93-98)
  • IssuerResponse (138-151)
  • IAgentOIDCIssuerCreate (106-106)
libs/common/src/response-messages/index.ts (1)
  • ResponseMessages (1-634)
libs/common/src/common.utils.ts (1)
  • getAgentUrl (70-140)
apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (5)
  • encodeIssuerPublicId (202-207)
  • normalizeJson (192-200)
  • extractTemplateIds (184-190)
  • buildCredentialConfigurationsSupported (378-417)
  • buildIssuerPayload (159-182)
apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (3)
  • CredentialOfferPayload (84-99)
  • buildCredentialOfferPayload (401-484)
  • buildCredentialOfferUrl (140-165)
🪛 GitHub Check: SonarCloud Code Analysis
apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts

[warning] 21-21: "number" is overridden by string in this union type.

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


[warning] 21-21: "string" is overridden by string in this union type.

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


[warning] 21-21: "boolean" is overridden by string in this union type.

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


[warning] 214-215: Remove this commented out code.

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


[warning] 221-226: Remove this commented out code.

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

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts

[warning] 395-395: Unexpected negated condition.

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


[warning] 26-26: Remove this unused import of 'Prisma'.

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


[failure] 179-183: Unexpected await of a non-Promise (non-"Thenable") value.

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


[warning] 396-396: Unexpected negated condition.

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


[failure] 728-728: Unexpected await of a non-Promise (non-"Thenable") value.

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


[warning] 984-984: Remove this useless assignment to variable "issuanceSessionId".

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


[failure] 211-211: Unexpected await of a non-Promise (non-"Thenable") value.

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


[failure] 95-95: Unexpected await of a non-Promise (non-"Thenable") value.

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


[failure] 336-340: Unexpected await of a non-Promise (non-"Thenable") value.

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


[warning] 243-251: Remove this commented out code.

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


[warning] 981-981: Remove this useless assignment to variable "contextCorrelationId".

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


[failure] 490-490: Unexpected await of a non-Promise (non-"Thenable") value.

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

🔇 Additional comments (9)
apps/oid4vc-verification/src/oid4vc-verification.service.ts (1)

160-173: LGTM: Proper null safety.

The guard at line 164 correctly handles both null and empty array cases from the repository.

libs/logger/src/logging.interceptor.ts (3)

12-17: LGTM: Constructor refactoring improves code clarity.

The rename from _logger to logger follows standard TypeScript conventions and improves readability throughout the file.


44-48: Good refactor: Centralized context ID setup improves maintainability.

The setupContextId method consolidates the three storage operations into a single, reusable method. The type signature correctly requires a string parameter, which will be enforced once the string validation is added at line 24.


23-23: Fix the fallback reference to avoid potential ReferenceError.

The fallback getHeaders references an undefined identifier. If rpcContext.getHeaders?.() is nullish, the code will try to access a standalone getHeaders variable that doesn't exist.

Apply this diff to fix the fallback:

-      const headers = rpcContext.getHeaders?.() ?? rpcContext.getHeaders;
+      const headers = rpcContext.getHeaders?.() ?? rpcContext.getHeaders ?? {};

This ensures you always have a valid object to work with, even if neither the method call nor the property exists.

Likely an incorrect or invalid review comment.

apps/api-gateway/src/x509/dtos/x509.dto.ts (2)

336-382: Well-structured search criteria DTO.

The X509SearchCriteriaDto properly implements pagination, filtering, and sorting with appropriate transformations and validation:

  • Query parameters are transformed using toNumber and trim helpers
  • Page size is bounded between 1 and 100
  • Enum validations ensure type safety
  • Default values provide sensible fallbacks

234-244: Union type requires explicit string validation handling.

The issuer and subject properties accept both X509CertificateIssuerAndSubjectOptionsDto and string types (DN format). The @ValidateNested() decorator only validates object instances, leaving string values completely unvalidated. While the interface intentionally supports both formats (as shown in the documentation), the DTO should either:

  • Add a custom validator to validate string DN format (e.g., with a regex or dedicated @IsDN() decorator)
  • Or use a discriminated union approach with separate validation paths for each type

Currently, malformed DN strings could pass through validation uncaught.

libs/common/src/common.utils.ts (1)

142-146: LGTM! Safe default for experimental features.

The function correctly gates experimental OIDC modules with a safe default (hidden when env var is unset or 'true'). The inverted logic (!hide) clearly expresses "should load when NOT hidden."

apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (1)

421-459: Webhook handler aligns with existing issuance pattern and looks sound

The storePresentationWebhook flow matches the issuance webhook pattern:

  • Normalizes the payload (type = 'Oid4vpPresentation', conditionally sets orgId when contextCorrelationId === 'default').
  • Delegates persistence/processing to oid4vcVerificationService.oid4vpPresentationWebhook.
  • Returns an IResponseType with statusCode: HttpStatus.CREATED and ResponseMessages.oid4vpSession.success.webhookReceived.
  • Optionally posts back to a configured webhook URL via _getWebhookUrl / _postWebhookResponse, logging but not throwing on webhook errors.

This is coherent and should behave well, assuming the service methods are implemented safely.

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts (1)

17-170: Gateway service wrappers look consistent with microservice API

The NATS payloads and subject names for issuers, templates, credential offers, and webhooks are assembled consistently and match the described microservice contracts. I don’t see correctness or safety issues here.

Comment on lines +103 to +115
export class NameListDto {
@ApiProperty({ type: [NameDto] })
@ArrayNotEmpty()
@IsArray()
@ValidateNested()
@Type(() => NameDto)
name: NameDto[];

@ApiPropertyOptional()
@IsOptional()
@IsBoolean()
markAsCritical?: boolean;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add { each: true } to ValidateNested for array validation.

The name property is an array of NameDto objects, but @ValidateNested() is missing the { each: true } option. Without this, the validator won't validate each array element properly, potentially allowing invalid data.

Apply this diff:

export class NameListDto {
  @ApiProperty({ type: [NameDto] })
  @ArrayNotEmpty()
  @IsArray()
-  @ValidateNested()
+  @ValidateNested({ each: true })
  @Type(() => NameDto)
  name: NameDto[];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class NameListDto {
@ApiProperty({ type: [NameDto] })
@ArrayNotEmpty()
@IsArray()
@ValidateNested()
@Type(() => NameDto)
name: NameDto[];
@ApiPropertyOptional()
@IsOptional()
@IsBoolean()
markAsCritical?: boolean;
}
export class NameListDto {
@ApiProperty({ type: [NameDto] })
@ArrayNotEmpty()
@IsArray()
@ValidateNested({ each: true })
@Type(() => NameDto)
name: NameDto[];
@ApiPropertyOptional()
@IsOptional()
@IsBoolean()
markAsCritical?: boolean;
}
🤖 Prompt for AI Agents
In apps/api-gateway/src/x509/dtos/x509.dto.ts around lines 103 to 115, the
NameListDto's name property uses @ValidateNested() but lacks the { each: true }
option so nested validation won't run for each array element; update the
decorator to @ValidateNested({ each: true }) (keeping @Type(() => NameDto) and
other decorators) so each NameDto in the name array is validated.

Comment on lines +270 to +305
function buildSdJwtCredential(
credentialRequest: CredentialRequestDtoLike,
templateRecord: any,
signerOptions: SignerOption[],
activeCertificateDetails?: X509CertificateRecord[]
): BuiltCredential {
// For SD-JWT format we expect payload to be a flat map of claims (no namespaces)
let payloadCopy = { ...(credentialRequest.payload as Record<string, unknown>) };

// // strip vct if present per requirement
// delete payloadCopy.vct;

const templateSignerOption: SignerOption = signerOptions.find(
(x) => templateRecord.signerOption.toLowerCase() === x.method
);
if (!templateSignerOption) {
throw new UnprocessableEntityException(
`Signer option "${templateRecord.signerOption}" is not configured for template ${templateRecord.id}`
);
}

if (templateRecord.signerOption === SignerMethodOption.X5C && credentialRequest.validityInfo) {
if (!activeCertificateDetails?.length) {
throw new UnprocessableEntityException('Active x.509 certificate details are required for x5c signer templates.');
}
const certificateDetail = activeCertificateDetails.find(
(x) => x.certificateBase64 === templateSignerOption.x5c?.[0]
);
if (!certificateDetail) {
throw new UnprocessableEntityException('No active x.509 certificate matches the configured signer option.');
}

const validationResult = validateCredentialDatesInCertificateWindow(
credentialRequest.validityInfo,
certificateDetail
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix signer mapping for X.509 SD‑JWT credentials (buildSdJwtCredential)

Here:

const templateSignerOption: SignerOption = signerOptions.find(
  (x) => templateRecord.signerOption.toLowerCase() === x.method
);

signerOptions[].method is 'did' | 'x5c' (from SignerMethodOption), but templateRecord.signerOption is the Prisma enum (DID, X509_P256, X509_ED25519, …). For X.509 templates, .toLowerCase() yields "x509_p256" / "x509_ed25519", which never matches "x5c", so you always hit the “Signer option not configured” path even when you’ve correctly populated signerOptions in the service.

Consider normalizing the DB value into a SignerMethodOption first, then matching on that, e.g.:

const rawSigner = (templateRecord.signerOption ?? '').toString().toLowerCase();
if (!rawSigner) {
  throw new UnprocessableEntityException('Template signerOption is not configured');
}

const expectedMethod =
  rawSigner === 'did' ? SignerMethodOption.DID : SignerMethodOption.X5C;

const templateSignerOption = signerOptions?.find((x) => x.method === expectedMethod);
if (!templateSignerOption) {
  throw new UnprocessableEntityException(
    `Signer option "${templateRecord.signerOption}" is not configured for template ${templateRecord.id}`
  );
}

This keeps DID templates working and lets X.509 templates correctly resolve their signer and certificate.

🤖 Prompt for AI Agents
In apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts around lines
270-305, the code compares templateRecord.signerOption.toLowerCase() directly to
signerOptions[].method causing X.509 enum values like "x509_p256" /
"x509_ed25519" to never match "x5c" and throw an incorrect
UnprocessableEntityException; normalize the DB enum to the expected
SignerMethodOption first (map any X.509 variants to SignerMethodOption.X5C and
DID to SignerMethodOption.DID), then find the templateSignerOption by matching
signerOptions[].method to that normalized value and keep the existing error
handling if no match is found.

Comment on lines +350 to +399
function buildMdocCredential(
credentialRequest: CredentialRequestDtoLike,
templateRecord: any,
signerOptions: SignerOption[],
activeCertificateDetails: X509CertificateRecord[]
): BuiltCredential {
let incomingPayload = { ...(credentialRequest.payload as Record<string, unknown>) };

if (
!credentialRequest.validityInfo ||
!credentialRequest.validityInfo.validFrom ||
!credentialRequest.validityInfo.validUntil
) {
throw new UnprocessableEntityException(`${ResponseMessages.oidcIssuerSession.error.missingValidityInfo}`);
}

if (!signerOptions?.length || !signerOptions[0].x5c?.length) {
throw new UnprocessableEntityException('An x5c signer configuration is required for mdoc credentials.');
}
if (!activeCertificateDetails?.length) {
throw new UnprocessableEntityException('Active x.509 certificate details are required for mdoc credentials.');
}
const certificateDetail = activeCertificateDetails.find((x) => x.certificateBase64 === signerOptions[0].x5c[0]);
if (!certificateDetail) {
throw new UnprocessableEntityException('No active x.509 certificate matches the configured signer option.');
}
const validationResult = validateCredentialDatesInCertificateWindow(
credentialRequest.validityInfo,
certificateDetail
);

if (!validationResult.isValid) {
throw new UnprocessableEntityException(`${JSON.stringify(validationResult.details)}`);
}
incomingPayload = {
...incomingPayload,
validityInfo: credentialRequest.validityInfo
};

const apiFormat = mapDbFormatToApiFormat(templateRecord.format);
const idSuffix = formatSuffix(apiFormat);
const credentialSupportedId = `${templateRecord.name}-${idSuffix}`;

return {
credentialSupportedId,
signerOptions: signerOptions ? signerOptions[0] : undefined,
format: apiFormat,
payload: incomingPayload
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hard‑wiring signerOptions[0] for mdoc credentials

buildMdocCredential currently assumes:

if (!signerOptions?.length || !signerOptions[0].x5c?.length) { ... }
...
const certificateDetail = activeCertificateDetails.find(
  (x) => x.certificateBase64 === signerOptions[0].x5c[0]
);
...
return {
  credentialSupportedId,
  signerOptions: signerOptions ? signerOptions[0] : undefined,
  // ...
};

For multi‑template offers or mixed signer configurations, this couples every mdoc credential to the first signer option and certificate, regardless of which template it belongs to.

To keep behavior predictable:

  • Either resolve a per‑template signer (similar to the SD‑JWT path) and pass a single ResolvedSignerOption into buildMdocCredential, or
  • At minimum, derive the correct signer based on templateRecord.signerOption instead of always index 0.

Right now, issuing multiple mdoc credentials with different signers will produce incorrect bindings.

🤖 Prompt for AI Agents
In apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts around lines
350–399, the function hard-wires signerOptions[0] which binds every mdoc to the
first signer; instead, select the signer that belongs to the current template
(e.g. resolve signer by templateRecord.signerOption identifier or thumbprint),
validate that this resolvedSigner exists and has x5c, pick the matching
activeCertificateDetails entry using resolvedSigner.x5c[0], run the same
certificate/date validations against that resolved signer/certificate, and
return that resolvedSigner (not signerOptions[0]) in the BuiltCredential; if
templateRecord.signerOption is missing, fall back to a clear error or existing
behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (8)
libs/common/src/response-messages/index.ts (2)

523-543: Avoid literal ${templateId} placeholder in success message.

oidcTemplate.success.deleteTemplate uses a single-quoted string with ${templateId}, so the placeholder is rendered literally and never interpolated. That’s inconsistent with error.failedDeleteTemplate, which expects the caller to append the ID.

Suggested change (keep callers appending the ID, like failedDeleteTemplate):

       getById: 'OID4VC template details fetched successfully.',
-      deleteTemplate: '[createTemplate] compensating delete succeeded for templateId=${templateId}'
+      deleteTemplate: '[createTemplate] compensating delete succeeded for templateId='

587-595: Session error string still references "template" instead of session.

In the OID4VP session block, error.deleteTemplate mentions "template", which is misleading in a verifier-session context.

Minimal fix to correct the user-facing wording while keeping the key name stable:

       deleteFailed: 'Failed to delete OID4VP session verifier.',
-      deleteTemplate: 'Error while deleting template.',
+      deleteTemplate: 'Error while deleting verification session.',
       responseNotFound: 'Verification session response not found.'

Optionally, you may also rename the key to deleteSession in a follow-up refactor and update usages to better reflect the domain.

apps/oid4vc-verification/src/oid4vc-verification.service.ts (6)

64-78: Fix undefined dereference in conflict handling.

When the agent responds with 409 before createdVerifierDetails is set, line 75 attempts to access .verifierId on undefined, throwing a TypeError and leaking a 500 error instead of the intended ConflictException. Use the already-known verifierId from line 47.

       } catch (error) {
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;
 
         if (status409) {
-          throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
+          throw new ConflictException(`Verifier with id '${verifierId}' already exists`);
         }
         throw error;
       }

108-139: Fix inverted guard and undefined dereference.

Line 109's condition 0 > existingVerifiers.length is logically inverted—it checks whether 0 is greater than the length, which is always false. This allows the code to proceed when existingVerifiers is null or empty, causing a crash at line 121 when accessing existingVerifiers[0].

Additionally, line 137 dereferences updatedVerifierDetails.verifierId when it may be undefined if the agent rejects with 409 before setting a response body.

       let updatedVerifierDetails;
       const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 > existingVerifiers.length) {
+      if (!existingVerifiers || existingVerifiers.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
@@
       } catch (error) {
         // We'll not need this
         const status409 =
           409 === error?.status?.message?.statusCode || 409 === error?.response?.status || 409 === error?.statusCode;
 
         if (status409) {
-          throw new ConflictException(`Verifier with id '${updatedVerifierDetails.verifierId}' already exists`);
+          const conflictId = existingVerifiers?.[0]?.publicVerifierId ?? verifierId;
+          throw new ConflictException(`Verifier with id '${conflictId}' already exists`);
         }
         throw error;
       }

175-205: Add null guard and use agent-facing ID in delete.

Line 179 checks only checkIdExist.length === 0 but doesn't guard against null, which getVerifiersByVerifierId can return. This causes a crash when the repository returns null.

Line 188 passes the internal verifierId to the agent, but lines 60 (create) and 121 (update) correctly use publicVerifierId for agent communication. The delete call should be consistent.

       const checkIdExist = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
-      if (0 == checkIdExist.length) {
+      if (!checkIdExist || checkIdExist.length === 0) {
         throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
       }
 
       const agentDetails = await this.oid4vpRepository.getAgentEndPoint(orgId);
       if (!agentDetails) {
         throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
       }
       const { agentEndPoint, id } = agentDetails;
-      const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_DELETE, checkIdExist[0].verifierId);
+      const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_DELETE, checkIdExist[0].publicVerifierId);
       this.logger.debug(`[deleteVerifierById] calling agent URL=${url}`);

234-249: Fix typo preventing X.509 certificate resolution.

Line 241 passes sessionRequest.requestSigner.methodv (undefined) to getCurrentActiveCertificate. The correct property is method (without the trailing v). This typo breaks all X.509-based verification flows with the error message at line 247.

         this.logger.debug('X5C based request signer method selected');
 
         const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
           orgId,
-          sessionRequest.requestSigner.methodv
+          sessionRequest.requestSigner.method
         );
         this.logger.debug(`activeCertificate=${JSON.stringify(activeCertificate)}`);

304-304: Remove duplicate await.

Line 304 has await await, which is redundant. The inner await already returns the resolved promise.

       this.logger.debug(`[getVerifierSession] calling agent URL=${url}`);
 
-      const verifiers = await await this._getOid4vpVerifierSession(url, orgId);
+      const verifiers = await this._getOid4vpVerifierSession(url, orgId);
       if (!verifiers || 0 === verifiers.length) {

331-331: Remove duplicate await.

Line 331 has await await, which is redundant.

       );
-      const verifiers = await await this._getOid4vpVerifierSession(url, orgId);
+      const verifiers = await this._getOid4vpVerifierSession(url, orgId);
       if (!verifiers || 0 === verifiers.length) {
🧹 Nitpick comments (1)
libs/common/src/response-messages/index.ts (1)

612-613: Polish x509 collision error message grammar.

The collisionForActivatingX5c message has several grammatical issues (“In order to active this you need to Inactivate…”). A clearer version will read better in UI/logs.

Proposed rewording:

-      collisionForActivatingX5c:
-        'Certificate date range collides with existing certificates for this organization, In order to active this you need to Inactivate the previous one.',
+      collisionForActivatingX5c:
+        'Certificate date range collides with existing certificates for this organization. To activate this certificate, you must first deactivate the previous one.',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c72b01e and c8781a8.

📒 Files selected for processing (2)
  • apps/oid4vc-verification/src/oid4vc-verification.service.ts (1 hunks)
  • libs/common/src/response-messages/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/oid4vc-verification/src/oid4vc-verification.service.ts (5)
libs/service/base.service.ts (1)
  • BaseService (2-9)
libs/common/src/common.utils.ts (1)
  • getAgentUrl (70-140)
libs/common/src/interfaces/x509.interface.ts (1)
  • X509CertificateRecord (203-215)
apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts (2)
  • RequestSigner (23-23)
  • Oid4vpPresentationWh (3-11)
apps/oid4vc-verification/interfaces/oid4vp-verifier.interfaces.ts (1)
  • VerificationSessionQuery (20-27)

Comment on lines +1 to +8
/* eslint-disable quotes */
/* eslint-disable no-useless-catch */
/* eslint-disable camelcase */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types, camelcase */

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Excessive ESLint disables mask code quality issues.

Seven disable directives suggest the code violates project standards. no-useless-catch indicates redundant try-catch blocks, no-console flags improper logging (confirmed at lines 264, 267), and no-explicit-any compromises type safety throughout.

Refactor to eliminate these violations rather than suppressing warnings.

🤖 Prompt for AI Agents
In apps/oid4vc-verification/src/oid4vc-verification.service.ts lines 1-8, the
file-wide ESLint disable comments mask real issues; remove these broad disables
and fix the underlying problems: eliminate redundant try/catch blocks flagged by
no-useless-catch (or rethrow with context), replace console.* uses at lines ~264
and ~267 with the project's logger, and remove any use of the implicit any type
by adding precise TypeScript types or generics; update naming to follow
conventions and add minimal targeted eslint overrides only where a justified
exception remains.

Comment on lines +264 to +267
console.log(`[oid4vpCreateVerificationSession] sessionRequest=${JSON.stringify(sessionRequest)}`);

const url = await getAgentUrl(agentEndPoint, CommonConstants.OID4VP_VERIFICATION_SESSION);
console.log(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace console.log with logger.

Lines 264 and 267 use console.log instead of this.logger.debug, which is the pattern used consistently throughout the service (lines 44, 208, 237, etc.). This breaks observability and violates the no-console ESLint rule disabled at line 4.

-      console.log(`[oid4vpCreateVerificationSession] sessionRequest=${JSON.stringify(sessionRequest)}`);
+      this.logger.debug(`[oid4vpCreateVerificationSession] sessionRequest=${JSON.stringify(sessionRequest)}`);
 
       const url = await getAgentUrl(agentEndPoint, CommonConstants.OID4VP_VERIFICATION_SESSION);
-      console.log(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);
+      this.logger.debug(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);
🤖 Prompt for AI Agents
In apps/oid4vc-verification/src/oid4vc-verification.service.ts around lines 264
to 267, replace the two console.log calls with this.logger.debug calls to match
the service's logging pattern: log the sessionRequest JSON using
this.logger.debug(`[oid4vpCreateVerificationSession]
sessionRequest=${JSON.stringify(sessionRequest)}`) and the agent URL call using
this.logger.debug(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);
remove the console.log statements and ensure imports/members already provide
this.logger (no other behavior changes).

tipusinghaw and others added 11 commits November 28, 2025 17:14
Signed-off-by: Tipu_Singh <[email protected]>
* feat: oidc issunace

Signed-off-by: Tipu_Singh <[email protected]>

* feat:create seperate microservice for oid4vc

Signed-off-by: Tipu_Singh <[email protected]>

* feat:removed logs

Signed-off-by: Tipu_Singh <[email protected]>

* refactor: removed duplicate code

Signed-off-by: Tipu_Singh <[email protected]>

* feat: create credential offer API

Signed-off-by: Tipu_Singh <[email protected]>

* fix: missing credential details repository logic

Signed-off-by: Tipu_Singh <[email protected]>

* feat: added docker file for oid4vc-issuance

Signed-off-by: Tipu_Singh <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
* refactor: update oid4vc flows

Signed-off-by: Tipu_Singh <[email protected]>

* refactor: removed commented code

Signed-off-by: Tipu_Singh <[email protected]>

* refactor: changed example

Signed-off-by: Tipu_Singh <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Rinkal Bhojani <[email protected]>
Signed-off-by: Rinkal Bhojani <[email protected]>
* added x509 cert for mdoc

Signed-off-by: Rinkal Bhojani <[email protected]>

* removed commented code

Signed-off-by: Rinkal Bhojani <[email protected]>

* removed commented code

Signed-off-by: Rinkal Bhojani <[email protected]>

---------

Signed-off-by: Rinkal Bhojani <[email protected]>
Signed-off-by: Krishna Waske <[email protected]>
Signed-off-by: Rinkal Bhojani <[email protected]>
tipusinghaw and others added 25 commits November 28, 2025 17:14
Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
* feat:oid4vc verifications

Signed-off-by: Tipu_Singh <[email protected]>

* feat: CRU for verifier

Signed-off-by: Krishna Waske <[email protected]>

* fix: get verifier api

Signed-off-by: Krishna Waske <[email protected]>

* feat: add delete verifier

Signed-off-by: Krishna Waske <[email protected]>

* fix: delete verifier reselient

Signed-off-by: Krishna Waske <[email protected]>

* feat: oid4vc verification verifier session (#1502)

* fix: verification session

Signed-off-by: Krishna Waske <[email protected]>

* feat: add verifier session response by id

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Krishna Waske <[email protected]>

* feat: verifier session post API (#1503)

* feat: verifier session post API

Signed-off-by: Tipu_Singh <[email protected]>

* fix: delete oid4vc verifier

Signed-off-by: Krishna Waske <[email protected]>

* fix: change DTO example

Signed-off-by: Krishna Waske <[email protected]>

* fix: change CREDEBL to example

Signed-off-by: Krishna Waske <[email protected]>

* fix: session to presentation

Signed-off-by: Krishna Waske <[email protected]>

* feat: added docker file for verification

Signed-off-by: Tipu_Singh <[email protected]>

* fix: remove unnecessary column for oid4vp_verifier table

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Krishna Waske <[email protected]>
Co-authored-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Krishna Waske <[email protected]>
Co-authored-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
* feat: debug logs init

Signed-off-by: Krishna Waske <[email protected]>

* fix: add missing debug logs

Signed-off-by: Krishna Waske <[email protected]>

* fix: exend base service for oid4vp

Signed-off-by: Krishna Waske <[email protected]>

* fix: nested response

Signed-off-by: Krishna Waske <[email protected]>

* fix: unwanted description in logs

Signed-off-by: Krishna Waske <[email protected]>

* fix: remove commented code

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Krishna Waske <[email protected]>
* feat: verification webhook implementation

Signed-off-by: Tipu_Singh <[email protected]>

* fix: batch size and nats config

Signed-off-by: Tipu_Singh <[email protected]>

* fix: review comment

Signed-off-by: Tipu_Singh <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
* feat: trace contextId across services, including passed from headers of http request

Signed-off-by: Krishna Waske <[email protected]>

* fix: import issues caused by merge

Signed-off-by: Krishna Waske <[email protected]>

* fix: contextId refactoring and coderabbit resolve

Signed-off-by: Krishna Waske <[email protected]>

* fix: make contextInterceptorModule resilient

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Krishna Waske <[email protected]>
* feat: add optionally enable disable oidc module

Signed-off-by: Krishna Waske <[email protected]>

* chore: add some descriptive logs

Signed-off-by: Krishna Waske <[email protected]>

* fix: update the registration of experimental modules

Signed-off-by: Krishna Waske <[email protected]>

* fix: common function from utils

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Krishna Waske <[email protected]>
* fix: nats call from nats client for verification service

Signed-off-by: Krishna Waske <[email protected]>

* fix: ledger nats fix in agent-service

Signed-off-by: Krishna Waske <[email protected]>

* fix: remove unused variable

Signed-off-by: Krishna Waske <[email protected]>

* fix: nats calls in connection service

Signed-off-by: Krishna Waske <[email protected]>

* fix: webhook service unwanted imports

Signed-off-by: Krishna Waske <[email protected]>

* chore: add todos to remove nested mapping

Signed-off-by: Krishna Waske <[email protected]>

* fix: sonar cube issues

Signed-off-by: Krishna Waske <[email protected]>

* fix: coderabbit suggestions

Signed-off-by: Krishna Waske <[email protected]>

---------

Signed-off-by: Krishna Waske <[email protected]>
Signed-off-by: Rinkal Bhojani <[email protected]>
Signed-off-by: Rinkal Bhojani <[email protected]>
Signed-off-by: Tipu_Singh <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/agent-service/src/agent-service.service.ts (1)

501-585: Fix async fs.unlink throw and improve new ledger validation error causes

Two issues in this block:

  1. Throwing from async fs.unlink callback can crash the process

Inside the fs.unlink callback you do:

fs.unlink(filePath, (err) => {
  if (err) {
    this.logger.error(`Error removing file: ${err.message}`);
    throw new InternalServerErrorException(err.message);
  }
  ...
});

Because this throw happens in an async callback, it will not be caught by the surrounding try/catch and can terminate the Node process if file removal fails (e.g., file missing or permission error).

Refactor to an awaited promise-based call so errors stay within the try/catch:

-        if (agentDetails?.agentToken) {
-          fs.unlink(filePath, (err) => {
-            if (err) {
-              this.logger.error(`Error removing file: ${err.message}`);
-              throw new InternalServerErrorException(err.message);
-            } else {
-              this.logger.log(`File ${filePath} has been removed successfully`);
-            }
-          });
-        }
+        if (agentDetails?.agentToken) {
+          try {
+            await fs.promises.unlink(filePath);
+            this.logger.log(`File ${filePath} has been removed successfully`);
+          } catch (err) {
+            this.logger.error(`Error removing file: ${(err as Error).message}`);
+            // Decide whether to fail the whole spin-up or just log the issue; 
+            // if non-fatal, drop the throw here.
+          }
+        }
  1. new Error() without message in the new ledger checks

When ledgerList is falsy you throw:

throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, {
  cause: new Error(),
  description: ResponseMessages.errorMessages.notFound
});

Using a bare new Error() loses context and is what Sonar is flagging. Prefer passing the same message you surface or omitting cause entirely:

-        if (!ledgerList) {
-          throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, {
-            cause: new Error(),
-            description: ResponseMessages.errorMessages.notFound
-          });
-        }
+        if (!ledgerList) {
+          throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, {
+            cause: new Error(ResponseMessages.agent.error.invalidLedger),
+            description: ResponseMessages.errorMessages.notFound
+          });
+        }

The rest of the spin-up flow (ledger existence check against _getALlLedgerDetails, base wallet token derivation, encrypted API key, and agent details persistence) looks coherent.

♻️ Duplicate comments (68)
.env.sample (1)

126-127: Fix incorrect comments and add quotes to NKEY seed values.

The comments for lines 126-127 both incorrectly reference "x509 service" when they should specify the correct services. Additionally, the placeholder values lack quotes which is inconsistent with the previous review feedback and dotenv best practices.

Apply this diff to fix the comment accuracy and value quoting:

-OIDC4VC_ISSUANCE_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
-OIDC4VC_VERIFICATION_NKEY_SEED=xxxxxxxxxxx // Please provide Nkeys secret for x509 service
+OIDC4VC_ISSUANCE_NKEY_SEED="xxxxxxxxxxx" // Please provide Nkeys secret for OIDC4VC issuance service
+OIDC4VC_VERIFICATION_NKEY_SEED="xxxxxxxxxxx" // Please provide Nkeys secret for OIDC4VC verification service
libs/prisma-service/prisma/data/credebl-master-table.json (1)

140-168: Misaligned indentation in ledgerConfig persists from prior review.

The closing braces for the details object at lines 150, 159, and 166 remain at 4-space indentation instead of 6 spaces (matching the opening properties). This was flagged in the prior review; however, JSON remains syntactically valid since parsers ignore whitespace outside strings.

Per your previous decision to defer this, no action is required. If you decide to address formatting consistency in a future cleanup, a JSON formatter can correct the indentation automatically.

apps/api-gateway/src/helper-files/file-operation.helper.ts (2)

16-20: Hoist promisified readFile to module scope.

Promisifying inside the function creates a new promisified version on every call, which is inefficient. Move it to module scope like writeFile and mkdir.

Apply this diff:

 const writeFile = promisify(fs.writeFile);
 const mkdir = promisify(fs.mkdir);
+const readFile = promisify(fs.readFile);
 
 export const getFile = async (path: string, encoding: BufferEncoding): Promise<string | Buffer> => {
-  const readFile = promisify(fs.readFile);
-
   return encoding ? readFile(path, { encoding }) : readFile(path, {});
 };

22-26: Hoist promisified unlink to module scope.

Promisifying inside the function creates overhead on every call. Move it to module scope for consistency with writeFile and mkdir.

Apply this diff:

 const writeFile = promisify(fs.writeFile);
 const mkdir = promisify(fs.mkdir);
+const unlink = promisify(fs.unlink);
 
 export const deleteFile = async (path: string): Promise<void> => {
-  const unlink = promisify(fs.unlink);
-
   return unlink(path);
 };
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1)

298-348: Decouple requestSigner from Prisma SignerOption and simplify _oneOfCheck Swagger metadata

Two related points here:

  1. DTO coupling to Prisma and SignerOption name collision

    import { SignerOption } from '@prisma/client';
    ...
    @IsOptional()
    requestSigner?: {
      method: SignerOption;
    };
    • This DTO is importing SignerOption from @prisma/client, which couples your public API contract directly to the persistence layer.
    • There is also a separate SignerOption interface in apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts with a different shape ({ method: SignerMethodOption; did?: string; x5c?: string[]; }), so the same name now refers to different things in different layers, which is confusing and error‑prone.

    Recommended direction:

    • Introduce or reuse a domain‑level enum/interface (e.g., SignerMethodOption + a RequestSignerDto) from a shared interfaces module, and stop importing SignerOption from @prisma/client in this DTO.

    • Model requestSigner as a proper DTO with nested validation, e.g.:

      // from a shared domain module, not @prisma/client
      export enum SignerMethodOption {
        DID = 'DID',
        X5C = 'X5C',
        // ...
      }
      
      export class RequestSignerDto {
        @ApiProperty({ enum: SignerMethodOption, description: 'Signer method' })
        @IsEnum(SignerMethodOption)
        method: SignerMethodOption;
      
        @ApiPropertyOptional({ description: 'DID to use for signing' })
        @IsOptional()
        @IsString()
        did?: string;
      
        @ApiPropertyOptional({ type: [String], description: 'X.509 certificate chain (PEM)' })
        @IsOptional()
        @IsArray()
        @IsString({ each: true })
        x5c?: string[];
      }
      
      // In PresentationRequestDto:
      @ApiPropertyOptional({
        type: RequestSignerDto,
        description: 'Signer option type'
      })
      @IsOptional()
      @ValidateNested()
      @Type(() => RequestSignerDto)
      requestSigner?: RequestSignerDto;

    This avoids Prisma leakage, removes the naming collision, and gives you proper validation on the nested object.

  2. Make _oneOfCheck purely internal to Swagger

    @ApiHideProperty()
    @ApiPropertyOptional({
      description: 'Internal: ensures exactly one of dcql or presentationExchange is present'
    })
    @Validate(OnlyOneOfConstraint, ['dcql', 'presentationExchange'])
    _oneOfCheck?: any;

    Once @ApiHideProperty() is present, the extra @ApiPropertyOptional is redundant and may still confuse tooling. You can safely drop @ApiPropertyOptional and keep it as a hidden internal validation hook:

    @ApiHideProperty()
    @Validate(OnlyOneOfConstraint, ['dcql', 'presentationExchange'])
    _oneOfCheck?: any;
apps/webhook/src/webhook.service.ts (2)

28-53: Fix mismatch between declared return type and actual value in registerWebhook

registerWebhook is declared to return Promise<ICreateWebhookUrl> but currently returns webhookUrl.webhookUrl, which is just a string. This violates the declared interface and will either fail type-checking or surprise callers expecting an object.

Either change the return type to Promise<string> or, more in line with the interface, return an object that matches ICreateWebhookUrl. For example:

-        if (!webhookUrl) {
-          throw new InternalServerErrorException(ResponseMessages.webhook.error.registerWebhook);
-        } else {
-          return webhookUrl.webhookUrl;
-        }
+        if (!webhookUrl) {
+          throw new InternalServerErrorException(ResponseMessages.webhook.error.registerWebhook);
+        }
+
+        return { webhookUrl: webhookUrl.webhookUrl };

56-69: Align getWebhookUrl return type with implementation

getWebhookUrl is declared to return Promise<IGetWebhookUrl> but actually returns webhookUrlInfo.webhookUrl (a string). Same issue as above: the implementation does not satisfy the declared interface and breaks the contract for callers.

Either change the return type to Promise<string> or return an object compatible with IGetWebhookUrl. For example:

-      if (!webhookUrlInfo) {
-        throw new NotFoundException(ResponseMessages.webhook.error.notFound);
-      }
-
-      return webhookUrlInfo.webhookUrl;
+      if (!webhookUrlInfo) {
+        throw new NotFoundException(ResponseMessages.webhook.error.notFound);
+      }
+
+      return { webhookUrl: webhookUrlInfo.webhookUrl };
apps/agent-service/src/agent-service.service.ts (1)

800-807: Keep ledger validation consistent and give Error a message here as well

The new shared-agent tenant path mirrors the ledger validation you added in _agentSpinup, which is good for consistency. As there, you construct a BadRequestException using cause: new Error() when ledgerList is falsy.

Apply the same improvement here (pass a descriptive message into Error or drop the cause field) so Sonar stops flagging it and logs contain meaningful context.

libs/common/src/nats.config.ts (1)

17-18: NATS queue group removal issue already flagged.

The queue assignment remains commented out, which was previously identified as a critical behavior change affecting 30+ microservices. The trailing comma after reconnectTimeWait is also still missing.

libs/common/src/common.utils.ts (2)

70-70: Breaking signature change from async to sync already flagged.

The getAgentUrl signature change from async to sync was previously identified with 30+ call sites using await. The author acknowledged this will be addressed later.


108-108: Incorrect URL mapping for OIDC_DELETE_CREDENTIAL_OFFER already flagged.

This mapping issue was previously identified - OIDC_DELETE_CREDENTIAL_OFFER should map to URL_OIDC_ISSUER_SESSIONS_GET (with # placeholder) instead of URL_OIDC_ISSUER_SESSIONS_GET_ALL. The author acknowledged this will be improved later.

libs/common/src/cast.helper.ts (1)

527-542: Logic error and ESLint issues already flagged.

The flawed early-return condition on line 530 (queryParams?.length >= 0) and associated ESLint issues (missing semicolon, Yoda conditions, any type) were previously identified with a suggested fix using Object.keys(queryParams).length === 0.

apps/oid4vc-verification/src/oid4vc-verification.repository.ts (7)

42-57: Missing null check for findFirst result already flagged.

The destructuring of { agent } on line 45 without null-checking the findFirst result was previously identified as a major issue that will throw if no record is found.


59-75: Return type and misleading error message already flagged.

Both issues were previously identified: the return type should be Promise<org_agents | null>, and the error message incorrectly references "issuance repository" instead of "verification repository".


101-120: Incorrect error message already flagged; add type annotations.

The error message referencing "createOid4vpVerifier" was previously identified. Additionally, add type annotations similar to the createOid4vpVerifier method:

-  async updateOid4vpVerifier(verifierDetails, userId: string, verifierId: string): Promise<object> {
+  async updateOid4vpVerifier(
+    verifierDetails: { clientMetadata: object },
+    userId: string,
+    verifierId: string
+  ): Promise<oid4vp_verifier> {

138-155: Incorrect error message already flagged; return type includes unnecessary | null.

The error message referencing "getVerifiersByPublicVerifierId" was previously identified. Additionally, findMany never returns null:

-  async getVerifiersByVerifierId(orgId: string, verifierId?: string): Promise<oid4vp_verifier[] | null> {
+  async getVerifiersByVerifierId(orgId: string, verifierId?: string): Promise<oid4vp_verifier[]> {

157-174: Prisma findUnique usage and incorrect error message already flagged.

Both issues were previously identified: findUnique doesn't support nested where conditions (use findFirst), and the error message incorrectly references "getVerifiersByPublicVerifierId".


176-193: Prisma delete with nested where condition already flagged.

The issue with using nested conditions in delete was previously identified. The suggested alternatives (use deleteMany, verify at service layer, or two-step approach) were discussed, and the author expressed preference to avoid extra DB calls.


234-261: Return type should allow null - already flagged.

The return type issue was previously identified: findFirst can return null, so the signature should be Promise<X509CertificateRecord | null>.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-credential-wh.dto.ts (1)

34-36: Enable nested validation for credentialOfferPayload.

The credentialOfferPayload property lacks @ValidateNested() and @Type(() => CredentialOfferPayloadDto) decorators, so the validator won't enforce the @IsArray() constraint on credential_configuration_ids within the nested DTO. Malformed payloads will pass validation, and extractCredentialConfigurationIds will silently return [].

Apply this diff:

   @ApiProperty({ type: CredentialOfferPayloadDto })
   @IsObject()
+  @ValidateNested()
+  @Type(() => CredentialOfferPayloadDto)
   credentialOfferPayload!: CredentialOfferPayloadDto;
apps/api-gateway/src/x509/x509.controller.ts (1)

26-26: Fix IResponse import—use named import instead of default.

Line 26 imports IResponse as a default export, but the interface is exported as a named export in libs/common/src/interfaces/response.interface.ts. This will cause a TypeScript compilation error.

Apply this diff:

-import IResponse from '@credebl/common/interfaces/response.interface';
+import { IResponse } from '@credebl/common/interfaces/response.interface';
apps/api-gateway/src/x509/dtos/x509.dto.ts (1)

103-109: Add { each: true } to ValidateNested for array validation.

The name property is an array of NameDto objects, but @ValidateNested() is missing the { each: true } option. Without it, the validator won't properly validate each array element, potentially allowing invalid NameDto objects.

Apply this diff:

   @ApiProperty({ type: [NameDto] })
   @ArrayNotEmpty()
   @IsArray()
-  @ValidateNested()
+  @ValidateNested({ each: true })
   @Type(() => NameDto)
   name: NameDto[];
apps/verification/src/verification.service.ts (1)

238-249: Inconsistent error extraction pattern with risky fallback.

Line 242 uses error?.error?.reason || error?.message as a fallback, then calls .includes('not found') on it. If error?.error?.reason is undefined and error?.message is not a string (or is undefined), the .includes() call could throw a runtime error.

This pattern differs from line 412, which uses only error?.error?.reason without a fallback. Consider standardizing on a defensive pattern that checks string type before calling .includes().

Apply this diff:

-      const errorMessage = error?.error?.reason || error?.message;
+      const errorMessage = error?.error?.reason;
 
-      if (errorMessage?.includes('not found')) {
+      if (typeof errorMessage === 'string' && errorMessage.includes('not found')) {
         throw new NotFoundException(errorMessage);
       }
+      else if (!errorMessage && error?.message) {
+        throw new RpcException(error.message);
+      }
 
       throw new RpcException(error);
apps/oid4vc-issuance/test/app.e2e-spec.ts (1)

18-18: Remove HTTP test for NATS microservices module.

The Oid4vcIssuanceModule is configured exclusively for NATS message-driven communication with only @MessagePattern handlers, not HTTP routes. This test attempts a GET request to "/", which doesn't exist, and will fail.

Either remove this test file entirely if HTTP testing isn't needed, or remove just this test:

-  it('/ (GET)', () => request(app.getHttpServer()).get('/').expect(200).expect('Hello World!'));
apps/oid4vc-issuance/src/main.ts (1)

15-15: Verify NATS service name consistency with gateway module.

This microservice uses CommonConstants.OIDC4VC_ISSUANCE_SERVICE, but the corresponding gateway module at line 17 of apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.module.ts uses CommonConstants.ISSUANCE_SERVICE. If these constants differ, NATS message routing will fail.

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.module.ts (1)

17-17: Critical: NATS service name mismatch will break message routing.

The gateway module uses CommonConstants.ISSUANCE_SERVICE, but the OID4VC issuance microservice (apps/oid4vc-issuance/src/main.ts:15) uses CommonConstants.OIDC4VC_ISSUANCE_SERVICE. This mismatch prevents the gateway from communicating with the microservice.

Apply this diff:

-        options: getNatsOptions(CommonConstants.ISSUANCE_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
+        options: getNatsOptions(CommonConstants.OIDC4VC_ISSUANCE_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
apps/oid4vc-verification/src/main.ts (1)

17-18: Uncomment the global exception filter for consistency.

All other NATS microservices in the codebase use app.useGlobalFilters(new HttpExceptionFilter()) for consistent exception handling. This service should follow the same pattern.

Uncomment line 18:

   app.useLogger(app.get(NestjsLoggerServiceAdapter));
-  // TODO: Not sure if we want the below
-  // app.useGlobalFilters(new HttpExceptionFilter());
+  app.useGlobalFilters(new HttpExceptionFilter());

You'll also need to import HttpExceptionFilter:

import { HttpExceptionFilter } from 'libs/http-exception.filter';
apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts (7)

161-161: Verify service method name matches controller call.

The controller calls oidcGetIssuerById, but the service may define a different method name (e.g., oidcIssuerGetById). Verify the service API matches this call.


191-191: Verify service method name matches controller call.

The controller calls oidcGetIssuers, but the service may define a different method name (e.g., oidcIssuers). Verify the service API matches this call.


259-259: Verify service signature matches controller call.

The controller passes (CredentialTemplate, user, orgId, issuerId), but the service signature may differ in argument order or count. Verify the service expects these parameters in this order.


381-381: Verify service signature matches controller call.

The controller passes (user, orgId, templateId, dto, issuerId), but the service signature may differ. Verify parameter order and types match the service definition.


431-431: Verify service signature matches controller call.

The controller passes (user, orgId, templateId, issuerId) to deleteTemplate. Verify the service signature matches, particularly that issuerId is included.


556-556: Verify service method name matches controller call.

The controller calls getAllCredentialOffers, but the service may define getCredentialOffers instead. Verify the method name matches.


583-587: HTTP 204 should not include a response body.

The endpoint returns HttpStatus.NO_CONTENT (204) but includes a JSON body. HTTP 204 means "no content" and should not have a response body. Either change to HttpStatus.OK (200) if a body is needed, or remove the body for 204.

Apply one of these fixes:

Option 1: Return 200 with body:

     const finalResponse: IResponse = {
-      statusCode: HttpStatus.NO_CONTENT,
+      statusCode: HttpStatus.OK,
       message: ResponseMessages.oidcIssuerSession.success.delete,
       data: deletedofferDetails
     };
-    return res.status(HttpStatus.NO_CONTENT).json(finalResponse);
+    return res.status(HttpStatus.OK).json(finalResponse);

Option 2: Return 204 without body:

-    const finalResponse: IResponse = {
-      statusCode: HttpStatus.NO_CONTENT,
-      message: ResponseMessages.oidcIssuerSession.success.delete,
-      data: deletedofferDetails
-    };
-    return res.status(HttpStatus.NO_CONTENT).json(finalResponse);
+    return res.status(HttpStatus.NO_CONTENT).send();
apps/verification/src/verification.controller.ts (1)

48-50: Tighten Promise<object> / Promise<object | object[]> return types to domain interfaces.

These three endpoints still return very broad object types even though the module already defines richer interfaces (e.g., IProofPresentationDetails, IProofPresentation, etc.). This weakens type safety and tooling support.

Consider updating the signatures to use concrete response types aligned with what VerificationService actually returns (for example, Promise<IProofPresentationDetails> for getProofPresentationById and a suitable domain union for sendProofRequest / verifyPresentation), and adjust the service typings accordingly.

Also applies to: 68-73, 82-84

libs/logger/src/logging.interceptor.ts (1)

23-33: Add explicit string guard when reading contextId from headers.

contextIdFromHeader is currently any truthy value returned by headers.get('contextId'). A non-string truthy value (e.g., number or object) will pass the if (contextIdFromHeader) check and be stored as the correlation ID, even though setupContextId is typed to take a string.

Refine the extraction to enforce a non-empty string:

-      const contextIdFromHeader = headers && 'function' === typeof headers.get ? headers.get('contextId') : undefined;
+      const rawContextId = headers && typeof headers.get === 'function' ? headers.get('contextId') : undefined;
+      const contextIdFromHeader =
+        typeof rawContextId === 'string' && rawContextId ? rawContextId : undefined;

This ensures only valid string IDs are re-used; everything else falls back to generating a new UUID.

Also applies to: 44-47

apps/x509/src/x509.controller.ts (1)

31-33: Normalize method naming and user parameter usage in X.509 controller.

Two issues remain here:

  • deActivateCertificate / 'deActivate-x509-certificate' are inconsistently cased relative to activateCertificate; prefer deactivateCertificate and 'deactivate-x509-certificate' for consistency.
  • getCertificateByOrgId and getCertificate accept user: IUserRequest but never use it, while the mutating endpoints use Prisma user. Either:
    • Remove user from the read-only endpoints, or
    • Standardize on a single user type (e.g., IUserRequest) across all methods and propagate it through the service layer if needed.

Also applies to: 36-48

apps/x509/src/interfaces/x509.interface.ts (1)

4-13: Align status and OrgAgent typings with existing enums and DB nullability.

Two existing concerns still apply:

  • CreateX509CertificateEntity.status is typed as string while other DTOs use x5cRecordStatus. Using the enum here as well would constrain creation-time values and keep status handling consistent.
  • OrgAgent currently marks all fields as required, but several are nullable in the Prisma schema (e.g., orgDid, verkey, agentEndPoint, agentId, isDidPublic, ledgerId, orgAgentTypeId, tenantId). These should be optional (?) to match DB reality and avoid incorrect non-null assumptions downstream.

Also applies to: 29-43

apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts (2)

61-67: Fix misleading JSDoc for verifier create/update methods.

Both JSDoc blocks still describe “Create issuer against a org(tenant)” and mention deletion status, while the methods actually create/update verifiers:

  • Line 61–67 for oidcIssuerCreate
  • Line 106–113 for oidcIssuerUpdate

Update the comments to accurately reflect verifier operations and parameters (orgId, verifierId, create/update DTO, response). Renaming the methods to something like createVerifier / updateVerifier would also reduce confusion, but the minimum is to correct the JSDoc so it matches current behavior.

Also applies to: 106-113


308-413: Normalize response envelope for presentation GET endpoints.

getVerificationPresentation and getVerificationPresentationResponse return a custom shape:

return res.status(HttpStatus.OK).json({
  success: true,
  message: '...retrieved successfully.',
  data: result
});

while the rest of the controller (and ApiResponseDto) use the shared IResponse/IResponseType contract with statusCode and ResponseMessages.*.

For consistency and to avoid surprising clients, consider:

-      return res.status(HttpStatus.OK).json({
-        success: true,
-        message: 'Verifier details retrieved successfully.',
-        data: result
-      });
+      const finalResponse: IResponse = {
+        statusCode: HttpStatus.OK,
+        message: ResponseMessages.oid4vpSession.success.fetch,
+        data: result
+      };
+      return res.status(HttpStatus.OK).json(finalResponse);

and a similar change in getVerificationPresentationResponse using the appropriate ResponseMessages.oid4vpSession.* key.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts (1)

156-179: Add validation and API metadata for IssuerUpdationDto.issuerId.

issuerId is currently undecorated, so it’s effectively any at runtime, skipped by class-validator and Swagger. Given it’s used to identify which issuer to update, this should be constrained to a string and marked optional/explicit in the contract. This was already raised on a previous commit and still applies.

 export class IssuerUpdationDto {
-  issuerId?: string;
+  @ApiPropertyOptional({
+    description: 'Optional override for the issuer identifier',
+    example: 'credebl-university'
+  })
+  @IsOptional()
+  @IsString({ message: 'issuerId must be a string' })
+  issuerId?: string;

You may also want to drop the redundant @ApiProperty on batchCredentialIssuanceSize and keep only @ApiPropertyOptional for clarity.

apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts (1)

240-253: Enforce template as required in CreateCredentialTemplateDto.

template is mandatory for both SD-JWT and mDL, but there’s no @IsDefined() guard. Validators will accept payloads with template omitted, and downstream code will receive undefined, causing runtime errors when accessing its structure. This matches an earlier review comment and is still unresolved.

   @ApiProperty({
     type: Object,
     oneOf: [{ $ref: getSchemaPath(SdJwtTemplateDto) }, { $ref: getSchemaPath(MdocTemplateDto) }],
     description: 'Credential template definition (depends on credentialFormat)'
   })
+  @IsDefined({ message: 'template is required' })
   @ValidateNested()
   @Type(({ object }) => {
apps/api-gateway/src/connection/dtos/connection.dto.ts (1)

71-83: Type and validate orgId in CreateOutOfBandConnectionInvitation.

orgId is still declared without a type or decorators:

  invitationDid?: string;

  orgId;

This leaves it as any and outside Swagger/class-validator, which is risky for a routing/authorization field.

Align it with other string fields:

   @ApiPropertyOptional()
   @IsString()
   @IsOptional()
   @IsNotEmpty({ message: 'Please provide invitation did' })
   invitationDid?: string;
 
-  orgId;
+  @ApiPropertyOptional()
+  @IsString()
+  @IsOptional()
+  @IsNotEmpty({ message: 'Please provide orgId' })
+  orgId?: string;

If orgId is meant to be injected from context rather than client-supplied, consider removing it from this DTO entirely to avoid confusion.

apps/x509/src/repositories/x509.repository.ts (1)

124-145: Fix findById to use findFirst (relation filters are invalid on findUnique).

findById still calls findUnique with a relation filter:

const certificate = await this.prisma.x509_certificates.findUnique({
  where: {
    id,
    org_agents: { orgId }
  }
});

findUnique only accepts a WhereUnique input; relation filters like org_agents are not supported and will either fail or ignore the org constraint. Use findFirst (or a composite unique) instead:

-      const certificate = await this.prisma.x509_certificates.findUnique({
+      const certificate = await this.prisma.x509_certificates.findFirst({
         where: {
           id,
           org_agents: {
             orgId
           }
         }
       });

The NotFoundException logic below can remain unchanged.

apps/oid4vc-issuance/src/oid4vc-issuance.repository.ts (3)

40-53: Handle missing org agent type before destructuring.

getOrgAgentType destructures agent directly from findFirst:

const { agent } = await this.prisma.org_agents_type.findFirst({ where: { id: orgAgentId } });
return agent;

If no record exists, this throws a TypeError before you can raise a meaningful 404. Use an intermediate variable and explicit not-found handling:

   async getOrgAgentType(orgAgentId: string): Promise<string> {
     try {
-      const { agent } = await this.prisma.org_agents_type.findFirst({
-        where: {
-          id: orgAgentId
-        }
-      });
-
-      return agent;
+      const record = await this.prisma.org_agents_type.findFirst({
+        where: { id: orgAgentId }
+      });
+
+      if (!record) {
+        throw new NotFoundException(`Org agent type not found for id=${orgAgentId}`);
+      }
+
+      return record.agent;

55-66: Align getOrganizationByTenantId return type with findFirst (nullable).

getOrganizationByTenantId wraps findFirst but returns Promise<org_agents>:

async getOrganizationByTenantId(tenantId: string): Promise<org_agents> {
  return this.prisma.org_agents.findFirst({ where: { tenantId } });
}

Since findFirst can return null, this type is misleading. Either:

  • Change to Promise<org_agents | null> and keep current behavior, or
  • Throw a NotFoundException when no org agent is found and keep the non-nullable type.

Given current callers already null-check, updating the return type is likely the least disruptive.


161-188: Clarify nullability for getCurrentActiveCertificate (issuance).

Same pattern here: findFirst is used, but the method promises a non-null certificate:

async getCurrentActiveCertificate(orgId: string, keyType: x5cKeyType): Promise<X509CertificateRecord> {
  const certificate = await this.prisma.x509_certificates.findFirst({ ... });
  return certificate;
}

If there is no active certificate, this returns null despite the signature. Either:

  • Update the signature to Promise<X509CertificateRecord | null>, or
  • Throw a NotFoundException when certificate is missing to guarantee a non-null return.

Make sure the choice aligns with how issuers and verifiers handle “no active certificate” states elsewhere.

apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts (2)

42-51: Align CredentialConfig.claims type with actual claims object shape.

CredentialConfig declares:

type CredentialConfig = {
  ...
  claims: Claim[];
  ...
};

but both builders do:

const claims = buildClaimsFromTemplate(template); // Record<string, any>
...
claims

So at runtime claims is a nested object (Record<string, any>), not Claim[]. This mismatch can confuse callers and undermines type safety, and it was already raised in earlier reviews.

Either:

  • Change the type to match the object shape:
 type CredentialConfig = {
   format: string;
   vct?: string;
   scope: string;
   doctype?: string;
-  claims: Claim[];
+  claims: Record<string, any>;
   credential_signing_alg_values_supported: string[];
   cryptographic_binding_methods_supported: string[];
   display: { name: string; description?: string; locale?: string }[];
 };

or

  • Refactor buildClaimsFromTemplate and the builders to actually return Claim[].

Right now the implementation and contract diverge.

Also applies to: 312-327, 347-355


391-411: Make appearanceJson handling null‑safe before accessing .display.

coerceJsonObject can return null, but the code immediately casts and dereferences:

const appearanceJson = coerceJsonObject<unknown>(templateRow.appearance);

const displayConfigurations =
  (appearanceJson as Appearance).display?.map((displayEntry) => ({ ... })) ?? [];

If appearanceJson is null or a primitive, (appearanceJson as Appearance).display throws before ?. is applied.

Guard the cast:

   const appearanceJson = coerceJsonObject<unknown>(templateRow.appearance);
-
-  // Prepare the display configuration
-  const displayConfigurations =
-    (appearanceJson as Appearance).display?.map((displayEntry) => ({
+  const appearance =
+    appearanceJson && 'object' === typeof appearanceJson
+      ? (appearanceJson as Appearance)
+      : null;
+
+  const displayConfigurations =
+    appearance?.display?.map((displayEntry) => ({
       name: displayEntry.name,
       description: displayEntry.description,
       locale: displayEntry.locale,
       logo: displayEntry.logo

This keeps the builder robust when appearance is missing or malformed.

apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts (4)

269-301: Replace @ValidateNested on payload with object validation

CredentialDto.payload is typed as object and decorated with @ValidateNested(), but there is no DTO class or @Type hint. ValidateNested is effectively a no-op here and misleading.

Prefer explicit object validation:

@ApiProperty({ /* existing example */ })
@IsObject()
payload: Record<string, unknown>;

or, if the payload has a fixed structure, introduce a dedicated DTO and use @ValidateNested() with @Type(() => ThatDto).

[Suggest updating the decorator and type accordingly.]


346-377: Align example configs with PreAuthorizedCodeFlowConfigDto / AuthorizationCodeFlowConfigDto

The CreateCredentialOfferD2ADto examples include fields that do not exist on the DTOs:

  • preAuthorizedCodeFlowConfig example uses preAuthorizedCode, userPinRequired, and txCode.inputMode, but DTO only has txCode and authorizationServerUrl (and TxCodeDto.input_mode is spelled with underscore).
  • authorizationCodeFlowConfig example shows clientId, redirectUri, scope, state, but AuthorizationCodeFlowConfigDto only exposes authorizationServerUrl.

Swagger examples should reflect the actual DTO shape or the DTOs should be extended to match the intended API. As-is, generated docs are misleading and fields would be stripped by validation/whitelisting.


161-167: Add nested validation for validityInfo

CredentialRequestDto.validityInfo is a ValidityInfo object but only has @IsOptional(). Without @ValidateNested() and @Type(() => ValidityInfo), nested date constraints (validFrom/validUntil) won’t run, and invalid date payloads will slip through DTO validation.

Recommend:

@IsOptional()
@ValidateNested()
@Type(() => ValidityInfo)
validityInfo?: ValidityInfo;

251-253: Fix ESLint errors for x5c examples and mixed operators in exp

ESLint is flagging:

  • array-bracket-newline for multi-line x5c arrays (lines 251–253, 319–321).
  • no-mixed-operators for Date.now() + 5 * 365 * 24 * 60 * 60 * 1000 (line 295).

Minimal fixes:

-      x5c: [
-        'MIIB3jCCAZCgAwIBAgIQQfBdIK9v3TIzKR+1HjlixD...'
-      ]
+      x5c: ['MIIB3jCCAZCgAwIBAgIQQfBdIK9v3TIzKR+1HjlixD...']
-        exp: dateToSeconds(new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000))
+        exp: dateToSeconds(new Date(Date.now() + (5 * 365 * 24 * 60 * 60 * 1000)))

Apply the same single-line x5c pattern in all example blocks.

Also applies to: 319-321, 293-296

apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts (1)

44-69: Add explicit string types to orgId parameters

Methods oid4vpGetVerifier, oid4vpDeleteVerifier, oid4vpGetVerifierSession, and getVerificationSessionResponse omit a type for orgId, so it defaults to any under non‑strict configs:

async oid4vpGetVerifier(orgId, verifierId?: string): Promise<object> { ... }

For consistency with the rest of the service and better type safety, annotate:

async oid4vpGetVerifier(orgId: string, verifierId?: string): Promise<object> { ... }
async oid4vpDeleteVerifier(orgId: string, verifierId: string): Promise<object> { ... }
async oid4vpGetVerifierSession(orgId: string, query?: VerificationPresentationQueryDto): Promise<object> { ... }
async getVerificationSessionResponse(orgId: string, verificationSessionId: string): Promise<object> { ... }
apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (2)

350-399: Avoid hard-wiring signerOptions[0] in buildMdocCredential

buildMdocCredential assumes the first signer applies to every mdoc credential:

if (!signerOptions?.length || !signerOptions[0].x5c?.length) { ... }
...
const certificateDetail = activeCertificateDetails.find(
  (x) => x.certificateBase64 === signerOptions[0].x5c[0]
);
...
return {
  credentialSupportedId,
  signerOptions: signerOptions ? signerOptions[0] : undefined,
  ...
};

For multi-template offers or mixed signer configurations, this binds all mdocs to the first signer/certificate, regardless of template.

Prefer resolving the signer per template, similar to the SD‑JWT path (e.g., via templateRecord.signerOption), and use that resolved signer both for certificate matching and the returned signerOptions field.


270-345: Fix signer mapping and handle missing signerOptions in buildSdJwtCredential

Two issues here:

  1. Incorrect signer mapping for X.509 templates
const templateSignerOption: SignerOption = signerOptions.find(
  (x) => templateRecord.signerOption.toLowerCase() === x.method
);
...
if (templateRecord.signerOption === SignerMethodOption.X5C && credentialRequest.validityInfo) { ... }
  • SignerOption.method is 'did' | 'x5c', while DB templateRecord.signerOption uses enum values like DID, X509_P256, X509_ED25519. For X.509 templates, .toLowerCase() yields "x509_p256" / "x509_ed25519", which never equals "x5c". Result: X.509 templates can’t resolve their signer option and never enter the X5C validation path.

You need to normalize the DB enum into SignerMethodOption first, e.g.:

const rawSigner = (templateRecord.signerOption ?? '').toString().toLowerCase();
if (!rawSigner) {
  throw new UnprocessableEntityException('Template signerOption is not configured');
}

const expectedMethod =
  rawSigner === 'did' ? SignerMethodOption.DID : SignerMethodOption.X5C;

if (!Array.isArray(signerOptions)) {
  throw new UnprocessableEntityException('signerOptions must be provided for credential building');
}

const templateSignerOption = signerOptions.find((x) => x.method === expectedMethod);
if (!templateSignerOption) {
  throw new UnprocessableEntityException(
    `Signer option "${templateRecord.signerOption}" is not configured for template ${templateRecord.id}`
  );
}

and keep your existing X5C/date validation logic.

  1. signerOptions may be undefined

buildCredentialOfferPayload declares signerOptions?: SignerOption[] but calls:

return buildSdJwtCredential(credentialRequest, templateRecord, signerOptions, activeCertificateDetails);

If callers omit signerOptions, signerOptions.find(...) will throw. Either:

  • Make signerOptions required in both signatures, or
  • Guard inside buildSdJwtCredential and throw a clear UnprocessableEntityException when it’s missing.

Together, these fixes avoid crashes and make X.509 SD‑JWT templates usable.

apps/connection/src/connection.service.ts (1)

64-72: Tighten return types for invitation helpers instead of unknown / any

Both helpers are central to connection-invitation flows but are weakly typed:

async _createConnectionInvitation(...): Promise<unknown> { ... }
...
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async _createOutOfBandConnectionInvitation(...): Promise<any> { ... }

Callers expect specific fields:

  • createConnectionInvitation?.invitationUrl
  • createConnectionInvitation?.invitationDid
  • createConnectionInvitation.outOfBandRecord.id

Define an interface, e.g.:

interface IConnectionInvitationResult {
  invitationUrl: string;
  invitationDid?: string;
  outOfBandRecord: { id: string };
}

and update signatures:

async _createConnectionInvitation(...): Promise<IConnectionInvitationResult> { ... }
async _createOutOfBandConnectionInvitation(...): Promise<IConnectionInvitationResult> { ... }

Then remove the any eslint-disable and let TypeScript enforce the shape across usages.

Also applies to: 572-585

apps/x509/src/x509.service.ts (2)

42-60: Guard required X.509 options before using nested fields

createCertificate assumes options.validity.notBefore/notAfter and options.authorityKey.keyType are always present. If options.validity or options.authorityKey is missing, you’ll get a TypeError before you can return a clean 4xx:

validFrom: options.validity.notBefore,
expiry: options.validity.notAfter,
keyType: options.authorityKey.keyType,

Add upfront validation and fail with a clear domain error:

const { options, user, orgId } = payload;

if (!options?.validity?.notBefore || !options?.validity?.notAfter || !options?.authorityKey?.keyType) {
  throw new UnprocessableEntityException(ResponseMessages.x509.error.errorCreate);
}

before building certificateDateCheckDto.

Also applies to: 84-93


145-183: Validate decoded certificate/publicKey shape before use in importCertificate

After _decodeX509CertificateForOrg, the code trusts decodedResult.response to contain publicKey.keyType and x509Certificate.notBefore/notAfter:

const { publicKey } = decodedResult.response;
const decodedCert = decodedResult.response.x509Certificate;
...
const isValidKeyType = Object.values(x5cKeyType).includes(publicKey.keyType as x5cKeyType);
const validFrom = new Date(decodedCert.notBefore);
const expiry = new Date(decodedCert.notAfter);

If the agent ever returns a partial/malformed response, these dereferences will throw.

Mirror the defensive checks suggested for createCertificate:

const { publicKey, x509Certificate } = decodedResult.response ?? {};
if (!publicKey?.keyType || !x509Certificate?.notBefore || !x509Certificate?.notAfter) {
  throw new UnprocessableEntityException(ResponseMessages.x509.error.errorDecode);
}

const validFrom = new Date(x509Certificate.notBefore);
const expiry = new Date(x509Certificate.notAfter);

Also applies to: 203-218

libs/common/src/common.constant.ts (1)

129-137: Resolve TODO on URL_OIDC_VERIFIER_CREATE before shipping

URL_OIDC_VERIFIER_CREATE is marked:

URL_OIDC_VERIFIER_CREATE = '/openid4vc/verifier', //TODO: correct this URL

This suggests the path is known to be wrong but is now a shared constant used by getAgentUrl and verifier flows. Once wired, debugging mismatched agent URLs will be painful.

Either correct the URL now (preferred) or gate any usage behind a feature flag and track a follow‑up task, but avoid merging with a known-bad constant.

apps/oid4vc-verification/src/oid4vc-verification.service.ts (4)

43-98: Avoid dereferencing undefined createdVerifierDetails on conflicts

In oid4vpCreateVerifier, the 409 path uses createdVerifierDetails.verifierId:

let createdVerifierDetails;
...
try {
  createdVerifierDetails = await this._createOid4vpVerifier(...);
  ...
} catch (error) {
  const status409 = ...;
  if (status409) {
    throw new ConflictException(`Verifier with id '${createdVerifierDetails.verifierId}' already exists`);
  }
  throw error;
}

If the agent responds with 409 before returning a body, createdVerifierDetails is still undefined, and this catch block will throw a new TypeError, masking the real conflict.

Use the known verifierId from the request instead:

if (status409) {
  throw new ConflictException(`Verifier with id '${verifierId}' already exists`);
}

and type createdVerifierDetails as VerifierRecord | undefined to make this clearer.


100-158: Fix verifier existence guard and conflict handling in oid4vpUpdateVerifier

Issues in the update flow:

  • Existence check is wrong:
const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
if (0 > existingVerifiers.length) { ... } // never true, and will throw if array is null
  • Conflict branch in the inner catch references updatedVerifierDetails.verifierId even when updatedVerifierDetails is still undefined.

Recommended adjustments:

const existingVerifiers = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
if (!existingVerifiers || existingVerifiers.length === 0) {
  throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
}
...
let updatedVerifierDetails: VerifierRecord | undefined;
try {
  const agentResult = await this._updateOid4vpVerifier(updateVerifier, url, orgId);
  if (!agentResult) {
    throw new InternalServerErrorException(ResponseMessages.oid4vp.error.updateFailed);
  }
  updatedVerifierDetails = agentResult.data as VerifierRecord;
} catch (error) {
  const status409 = ...;
  if (status409) {
    const conflictId = existingVerifiers?.[0]?.publicVerifierId ?? verifierId;
    throw new ConflictException(`Verifier with id '${conflictId}' already exists`);
  }
  throw error;
}

175-205: Guard against missing verifiers and use public ID for delete calls

In deleteVerifierById:

const checkIdExist = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
if (0 == checkIdExist.length) { ... } // will throw if null
...
const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_DELETE, checkIdExist[0].verifierId);
  • If the repository returns null, checkIdExist.length crashes.
  • For create/update flows, the agent-facing ID is publicVerifierId; deleting by internal verifierId risks a mismatch.

Consider:

const checkIdExist = await this.oid4vpRepository.getVerifiersByVerifierId(orgId, verifierId);
if (!checkIdExist || checkIdExist.length === 0) {
  throw new NotFoundException(ResponseMessages.oid4vp.error.notFound);
}

const { publicVerifierId, verifierId: internalId } = checkIdExist[0];
const url = await getAgentUrl(
  agentEndPoint,
  CommonConstants.OIDC_VERIFIER_DELETE,
  publicVerifierId ?? internalId
);

This avoids null crashes and keeps the agent call consistent with other flows.


207-283: Fix request signer keyType lookup and replace console.log with logger

Inside oid4vpCreateVerificationSession:

if (sessionRequest.requestSigner.method === SignerOption.X509_P256 || ...) {
  const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
    orgId,
    sessionRequest.requestSigner.methodv
  );
  ...
  requestSigner = {
    method: SignerMethodOption.X5C,
    x5c: [activeCertificate.certificateBase64]
  };
  activeCertificateDetails.push(activeCertificate);
}
...
console.log(`[oid4vpCreateVerificationSession] sessionRequest=${JSON.stringify(sessionRequest)}`);
const url = await getAgentUrl(...);
console.log(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);
  • RequestSigner does not expose methodv; this argument is always undefined, so getCurrentActiveCertificate cannot resolve a certificate and all x5c flows fail.
  • console.log bypasses the service logger and exists only because of a file‑wide no-console disable.

Fixes:

const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(
  orgId,
  sessionRequest.requestSigner.method
);
...
this.logger.debug(`[oid4vpCreateVerificationSession] sessionRequest=${JSON.stringify(sessionRequest)}`);
...
this.logger.debug(`[oid4vpCreateVerificationSession] calling agent URL=${url}`);

This aligns with the enum type and your logging pattern.

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (4)

200-211: Fix null-check to avoid dereferencing getIssuerDetails when missing

The condition:

if (!getIssuerDetails && !getIssuerDetails.publicIssuerId) {

will still evaluate getIssuerDetails.publicIssuerId when getIssuerDetails is null/undefined, causing a TypeError instead of a clean NotFoundException.

Use an OR and optional chaining so you short-circuit on a missing record:

-      if (!getIssuerDetails && !getIssuerDetails.publicIssuerId) {
+      if (!getIssuerDetails || !getIssuerDetails?.publicIssuerId) {
         throw new NotFoundException(ResponseMessages.oidcIssuer.error.notFound);
       }

This also aligns with the intended behavior previously discussed in older review comments.


265-292: deleteOidcIssuer uses the wrong source of truth and rethrows raw Error

Two issues here:

  1. Wrong type for issuerRecordId
    oidcIssuerGetById returns an issuer response from the agent, not the DB row, so:
const issuerRecordId = await this.oidcIssuerGetById(id, orgId);
if (!issuerRecordId.id) {

will almost certainly see issuerRecordId.id as undefined, making valid issuers look missing and building the delete URL with the wrong identifier.

You likely want the DB record instead:

-      const issuerRecordId = await this.oidcIssuerGetById(id, orgId);
-      if (!issuerRecordId.id) {
+      const issuerRecord = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(id);
+      if (!issuerRecord) {
         throw new NotFoundException(ResponseMessages.oidcIssuer.error.notFound);
       }
...
-      const url = await getAgentUrl(agentEndPoint, CommonConstants.OIDC_ISSUER_DELETE, issuerRecordId.id);
+      const url = getAgentUrl(
+        agentEndPoint,
+        CommonConstants.OIDC_ISSUER_DELETE,
+        issuerRecord.publicIssuerId
+      );

Adjust the identifier passed to the agent (publicIssuerId vs PK) to match the API contract you use elsewhere (e.g., oidcIssuerGetById).

  1. Inconsistent error propagation
    The catch block currently rethrows a raw Error, unlike the rest of the service which uses RpcException:
} catch (error) {
  if ('PrismaClientKnownRequestError' === error.name) {
    throw new BadRequestException(...);
  }
  throw new Error(error.response ? error.response : error);
}

Better to log and rethrow as RpcException:

-    } catch (error) {
-      if ('PrismaClientKnownRequestError' === error.name) {
-        throw new BadRequestException(error.meta?.cause ?? ResponseMessages.oidcIssuer.error.deleteFailed);
-      }
-      throw new Error(error.response ? error.response : error);
-    }
+    } catch (error) {
+      if ('PrismaClientKnownRequestError' === error?.name) {
+        throw new BadRequestException(error.meta?.cause ?? ResponseMessages.oidcIssuer.error.deleteFailed);
+      }
+      this.logger.error(`[deleteOidcIssuer] - error: ${JSON.stringify(error)}`);
+      throw new RpcException(error?.response ?? error);
+    }

This keeps RPC semantics consistent and avoids surfacing untyped Errors to callers.


468-497: DB/agent inconsistency risk in deleteTemplate (no rollback on agent failure)

deleteTemplate deletes the DB record before updating the agent, and does not attempt any rollback if _createOIDCTemplate fails:

const deleteTemplate = await this.oid4vcIssuanceRepository.deleteTemplate(templateId);
// ...
const createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId);
if (!createTemplateOnAgent) {
  throw new NotFoundException(...);
}

If the agent call fails, the template is gone from the DB but still present (or partially present) in the agent, leaving the system inconsistent. Contrast this with createTemplate and updateTemplate, which include compensation logic.

Either:

  • Flip the order (update agent first, then delete DB), or
  • Add rollback that recreates the DB row if the agent update fails.

Example rollback pattern (mirroring updateTemplate):

-      const deleteTemplate = await this.oid4vcIssuanceRepository.deleteTemplate(templateId);
+      const deleteTemplate = await this.oid4vcIssuanceRepository.deleteTemplate(templateId);
       if (!deleteTemplate) {
         throw new NotFoundException(ResponseMessages.oidcTemplate.error.deleteTemplate);
       }
...
-      const createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId);
+      const createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId);
       if (!createTemplateOnAgent) {
-        throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
+        // Roll back DB delete or surface a structured failure
+        await this.oid4vcIssuanceRepository.createTemplate(issuerId, template);
+        throw new RpcException(ResponseMessages.oidcTemplate.error.failedDeleteTemplate);
       }

(Exact rollback shape depends on what createTemplate expects, but the key point is to keep DB and agent in sync.)


627-675: Remove hard-coded 'Test' x5c stub and fix signerOptions.x5c type in D2A flow

In createOidcCredentialOfferD2A, for X.509 flows you still inject a hard-coded 'Test' value:

if (signerOptions.method === SignerMethodOption.X5C) {
  if (!signerOptions.x5c || 0 === signerOptions.x5c.length) {
    // const x5cFromDb = await this.oid4vcIssuanceRepository.getIssuerX5c(
    const x5cFromDb = 'Test';
    ...
    if (!x5cFromDb || 0 === x5cFromDb.length) {
      throw new BadRequestException(`No x5c found for issuer`);
    }
    signerOptions.x5c = x5cFromDb;
  }
}

Problems:

  • 'Test' produces invalid credential offers in any real environment.
  • signerOptions.x5c is supposed to be a string[], but you assign a string.

You should either wire the real DB lookup or fail fast when x5c is missing; until the DB wiring is ready, failing fast is safer than injecting a dummy value.

For a minimal safe fix:

-        if (signerOptions.method === SignerMethodOption.X5C) {
-          if (!signerOptions.x5c || 0 === signerOptions.x5c.length) {
-            // const x5cFromDb = await this.oid4vcIssuanceRepository.getIssuerX5c(
-            const x5cFromDb = 'Test';
-            ...
-            if (!x5cFromDb || 0 === x5cFromDb.length) {
-              throw new BadRequestException(`No x5c found for issuer`);
-            }
-            signerOptions.x5c = x5cFromDb;
-          }
-        }
+        if (signerOptions.method === SignerMethodOption.X5C) {
+          if (!Array.isArray(signerOptions.x5c) || signerOptions.x5c.length === 0) {
+            throw new BadRequestException(`No x5c found for issuer`);
+          }
+        }

If you do want automatic lookup, replace the stub with await this.oid4vcIssuanceRepository.getIssuerX5c(oidcCredentialD2APayload.publicIssuerId, orgId) and keep the string[] typing.

Also, consider updating the log/error message in this method ([createOidcCredentialOffer]) to reflect the D2A path name for easier debugging.

Comment on lines +119 to +155
@ApiResponse({ status: HttpStatus.OK, description: 'Verifier updated successfully.', type: ApiResponseDto })
@ApiBearerAuth()
@Roles(OrgRoles.OWNER)
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
async oidcIssuerUpdate(
@Param(
'orgId',
new ParseUUIDPipe({
exceptionFactory: (): Error => {
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
}
})
)
orgId: string,
@Param('verifierId')
verifierId: string,
@User() user: user,
@Body() updateVerifier: UpdateVerifierDto,
@Res() res: Response
): Promise<Response> {
this.logger.debug(`[oidcIssuerUpdate] Called with orgId=${orgId}, verifierId=${verifierId}, user=${user.id}`);
const updateVerifierRes = await this.oid4vcVerificationService.oid4vpUpdateVerifier(
updateVerifier,
orgId,
verifierId,
user
);

this.logger.debug(`[oidcIssuerUpdate] Verifier updated: ${updateVerifierRes.id}`);

const finalResponse: IResponse = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.oid4vp.success.update,
data: updateVerifierRes
};
return res.status(HttpStatus.OK).json(finalResponse);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align oidcIssuerUpdate status code in payload with HTTP response.

In oidcIssuerUpdate, the decorator and HTTP response use HttpStatus.OK, but the payload’s statusCode is HttpStatus.CREATED:

const finalResponse: IResponse = {
  statusCode: HttpStatus.CREATED, // ← mismatch
  message: ResponseMessages.oid4vp.success.update,
  data: updateVerifierRes
};
return res.status(HttpStatus.OK).json(finalResponse);

Set both to HttpStatus.OK for consistency:

-    const finalResponse: IResponse = {
-      statusCode: HttpStatus.CREATED,
+    const finalResponse: IResponse = {
+      statusCode: HttpStatus.OK,
       message: ResponseMessages.oid4vp.success.update,
       data: updateVerifierRes
     };
🤖 Prompt for AI Agents
In apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts
around lines 119-155, the JSON payload's statusCode is HttpStatus.CREATED while
the actual HTTP response and decorator use HttpStatus.OK; change the
finalResponse.statusCode value from HttpStatus.CREATED to HttpStatus.OK so the
payload matches the HTTP response (leave the res.status(HttpStatus.OK) as-is).

Comment on lines +963 to +1007
async storeOidcCredentialWebhook(
CredentialOfferWebhookPayload: Oid4vcCredentialOfferWebhookPayload
): Promise<object> {
try {
// pick fields
let organisationId: string;
const { oidcIssueCredentialDto, id } = CredentialOfferWebhookPayload;

if ('default' !== oidcIssueCredentialDto?.contextCorrelationId) {
const getOrganizationId = await this.oid4vcIssuanceRepository.getOrganizationByTenantId(
oidcIssueCredentialDto?.contextCorrelationId
);
organisationId = getOrganizationId?.orgId;
} else {
organisationId = id;
}

const {
contextCorrelationId,
credentialOfferPayload,
issuedCredentials,
id: issuanceSessionId
} = oidcIssueCredentialDto ?? {};
const cfgIds: string[] = Array.isArray(credentialOfferPayload?.credential_configuration_ids)
? credentialOfferPayload.credential_configuration_ids
: [];
const issuedCredentialsArr: string[] | undefined =
Array.isArray(issuedCredentials) && 0 < issuedCredentials.length
? issuedCredentials.map((c: any) => ('string' === typeof c ? c : JSON.stringify(c)))
: issuedCredentials && Array.isArray(issuedCredentials) && 0 === issuedCredentials.length
? []
: undefined;

const sanitized = {
...CredentialOfferWebhookPayload,
credentialOfferPayload: {
credential_configuration_ids: cfgIds
}
};

const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(
CredentialOfferWebhookPayload,
organisationId
);
return agentDetails;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Webhook storage: use the sanitized payload and guard organisationId

storeOidcCredentialWebhook computes a sanitized version of the payload and some derived fields, but then ignores them when persisting:

const sanitized = {
  ...CredentialOfferWebhookPayload,
  credentialOfferPayload: {
    credential_configuration_ids: cfgIds
  }
};

const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(
  CredentialOfferWebhookPayload,
  organisationId
);

Issues:

  • If getOrganizationByTenantId returns null, organisationId stays undefined, but you still call storeOidcCredentialDetails without failing fast.
  • If the intent of sanitized is to avoid storing full issuedCredentials or other sensitive blobs, that intent isn’t honored: you persist the original payload instead.

Given this is a webhook from issuance, it can easily include PII or full credential data. You probably want to either:

  1. Persist the sanitized payload and enforce a valid organisationId, or
  2. Delete the dead code and explicitly document that you intend to store the raw payload.

Example aligning with (1):

-      if ('default' !== oidcIssueCredentialDto?.contextCorrelationId) {
-        const getOrganizationId = await this.oid4vcIssuanceRepository.getOrganizationByTenantId(
-          oidcIssueCredentialDto?.contextCorrelationId
-        );
-        organisationId = getOrganizationId?.orgId;
-      } else {
-        organisationId = id;
-      }
+      if ('default' !== oidcIssueCredentialDto?.contextCorrelationId) {
+        const getOrganizationId = await this.oid4vcIssuanceRepository.getOrganizationByTenantId(
+          oidcIssueCredentialDto?.contextCorrelationId
+        );
+        organisationId = getOrganizationId?.orgId;
+        if (!organisationId) {
+          throw new NotFoundException('Organization not found for given tenant id');
+        }
+      } else {
+        organisationId = id;
+      }
...
-      const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(
-        CredentialOfferWebhookPayload,
-        organisationId
-      );
-      return agentDetails;
+      const record = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(sanitized, organisationId);
+      return record;

This both enforces a valid org context and ensures you only persist the stripped-down data you actually need.

🤖 Prompt for AI Agents
In apps/oid4vc-issuance/src/oid4vc-issuance.service.ts around lines 963-1007,
the function builds a sanitized payload and computes organisationId but then
persists the original payload and can call storeOidcCredentialDetails with
organisationId undefined; update the code to (1) validate organisationId after
getOrganizationByTenantId and throw or return a controlled error if no orgId is
found (fail-fast), and (2) pass the sanitized object (which contains only
credential_configuration_ids and omits issuedCredentials/full blobs) into
this.oid4vcIssuanceRepository.storeOidcCredentialDetails instead of the original
CredentialOfferWebhookPayload so sensitive/large data aren’t stored.

Comment on lines +248 to +303
// UTILITY - Find expiring certificates for an org
async findExpiringCertificatesByOrg(orgId: string, days: number = 30): Promise<X509CertificateRecord[]> {
try {
const expiryDate = new Date();
expiryDate.setDate(expiryDate.getDate() + days);

const certificates = await this.prisma.x509_certificates.findMany({
where: {
org_agents: {
orgId
},
expiry: {
lte: expiryDate
},
status: 'Active'
},
orderBy: {
expiry: 'asc'
}
});

return certificates;
} catch (error) {
this.logger.error(`Error in findExpiringCertificatesByOrg: ${error.message}`);
throw error;
}
}

async getCurrentActiveCertificate(orgId: string): Promise<X509CertificateRecord> {
try {
const now = new Date();

const certificate = await this.prisma.x509_certificates.findFirst({
where: {
org_agents: {
orgId
},
status: x5cRecordStatus.Active,
validFrom: {
lte: now
},
expiry: {
gte: now
}
},
orderBy: {
createdAt: 'desc'
}
});

return certificate;
} catch (error) {
this.logger.error(`Error in getCurrentActiveCertificate: ${error.message}`);
throw error;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the repository file and check its size
fd -e ts "x509.repository" -t f

Repository: credebl/platform

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Check file size
wc -l apps/x509/src/repositories/x509.repository.ts

Repository: credebl/platform

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Read the relevant section of the file
sed -n '240,310p' apps/x509/src/repositories/x509.repository.ts

Repository: credebl/platform

Length of output: 1797


🏁 Script executed:

#!/bin/bash
# Search for usages of getCurrentActiveCertificate
rg -n "getCurrentActiveCertificate" --type ts -A 3 -B 1

Repository: credebl/platform

Length of output: 4169


🏁 Script executed:

#!/bin/bash
# Check usage context at oid4vc-verification.service.ts
sed -n '235,250p' apps/oid4vc-verification/src/oid4vc-verification.service.ts

Repository: credebl/platform

Length of output: 658


🏁 Script executed:

#!/bin/bash
# Check usage at oid4vc-issuance.service.ts
sed -n '550,575p' apps/oid4vc-issuance/src/oid4vc-issuance.service.ts

Repository: credebl/platform

Length of output: 1006


Based on my verification, the review comment is correct and valid. Here's what I found:

Verification Summary:

  1. getCurrentActiveCertificate declares return type Promise<X509CertificateRecord> (non-nullable)
  2. ✅ The method uses findFirst() which returns T | null
  3. ✅ No null-check or exception is thrown before returning
  4. All callers are explicitly null-checking despite the type saying non-nullable:
    • oid4vc-verification.service.ts line 244: if (!activeCertificate) { throw new NotFoundException(...) }
    • oid4vc-issuance.service.ts lines 559-561 and 575-577: same pattern

This pattern confirms the developers know the method can return null, but the TypeScript type signature doesn't reflect this reality. It's a genuine type safety violation.


Align getCurrentActiveCertificate return type with nullable findFirst result.

getCurrentActiveCertificate uses findFirst:

const certificate = await this.prisma.x509_certificates.findFirst({ ... });
return certificate;

but declares:

async getCurrentActiveCertificate(orgId: string): Promise<X509CertificateRecord>

Since findFirst can return null, the return type must be nullable. Evidence: all callers (oid4vc-verification.service.ts, oid4vc-issuance.service.ts) explicitly null-check the result, indicating the type signature is incorrect.

Either make the return type nullable:

-  async getCurrentActiveCertificate(orgId: string): Promise<X509CertificateRecord> {
+  async getCurrentActiveCertificate(orgId: string): Promise<X509CertificateRecord | null> {

or throw NotFoundException when certificate is falsy before returning.

🤖 Prompt for AI Agents
In apps/x509/src/repositories/x509.repository.ts around lines 248-303,
getCurrentActiveCertificate is declared to return Promise<X509CertificateRecord>
but uses prisma.findFirst which can return null; update the method to accurately
reflect nullability or enforce non-null before returning: either change the
signature to Promise<X509CertificateRecord | null> and keep returning the
findFirst result, or keep the current non-nullable signature and throw a
NotFoundException (or other appropriate error) when certificate is falsy before
returning; update any related typings/imports and adjust callers if you choose
the nullable signature.

@@ -0,0 +1,43 @@
# Stage 1: Build the application
FROM node:18-alpine as build
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use uppercase "AS" for Dockerfile multi-stage build syntax.

Docker best practices recommend using uppercase keywords for Dockerfile instructions, including the multi-stage build alias.

Apply this diff:

-FROM node:18-alpine as build
+FROM node:18-alpine AS build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM node:18-alpine as build
FROM node:18-alpine AS build
🤖 Prompt for AI Agents
In Dockerfiles/Dockerfile.oid4vc-issuance around line 2, the multi-stage build
alias uses lowercase "as"; change "as" to uppercase "AS" so the FROM instruction
reads with "AS" to follow Dockerfile keyword casing conventions and best
practices.

Comment on lines +9 to +11
# Copy package.json and package-lock.json
COPY package.json ./
COPY pnpm-workspace.yaml ./
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update comment to match the actual code.

The comment on line 9 references copying package-lock.json, but the code copies pnpm-workspace.yaml instead.

Apply this diff:

-# Copy package.json and package-lock.json
+# Copy package.json and pnpm-workspace.yaml
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Copy package.json and package-lock.json
COPY package.json ./
COPY pnpm-workspace.yaml ./
# Copy package.json and pnpm-workspace.yaml
COPY package.json ./
COPY pnpm-workspace.yaml ./
🤖 Prompt for AI Agents
In Dockerfiles/Dockerfile.oid4vc-issuance around lines 9 to 11, the comment says
"Copy package.json and package-lock.json" but the commands copy package.json and
pnpm-workspace.yaml; update the comment to accurately reflect the files being
copied (e.g., "Copy package.json and pnpm-workspace.yaml") so the comment
matches the actual COPY instructions.

@@ -0,0 +1,43 @@
# Stage 1: Build the application
FROM node:18-alpine as build
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use uppercase "AS" for Dockerfile multi-stage build syntax.

Docker best practices and linters recommend using uppercase keywords for Dockerfile instructions, including the multi-stage build alias.

Apply this diff:

-FROM node:18-alpine as build
+FROM node:18-alpine AS build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM node:18-alpine as build
FROM node:18-alpine AS build
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 2-2: Replace "as" with upper case format "AS".

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

🤖 Prompt for AI Agents
In Dockerfiles/Dockerfile.x509 around line 2, the multi-stage build alias uses
lowercase "as" ("FROM node:18-alpine as build"); update it to use uppercase "AS"
to conform with Dockerfile syntax conventions and linters by changing the
instruction to "FROM node:18-alpine AS build".

@@ -0,0 +1,58 @@
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');
export class DateOnly {
private date: Date;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Make the date field readonly.

The date field is never reassigned after construction, so marking it as readonly enforces immutability and clarifies the design intent.

Apply this diff:

-  private date: Date;
+  private readonly date: Date;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private date: Date;
private readonly date: Date;
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 3-3: Member 'date' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZqQljVb_PQbb07SJZF-&open=AZqQljVb_PQbb07SJZF-&pullRequest=1517

🤖 Prompt for AI Agents
In libs/common/src/date-only.ts around line 3, the private field declaration
"private date: Date;" should be made readonly since it's only assigned in the
constructor and never reassigned; update the declaration to "private readonly
date: Date;" so the field is immutable and signals intent.

* feat: dcapi support

Signed-off-by: Tipu_Singh <[email protected]>

* feat: added id validation in create presentation

Signed-off-by: Tipu_Singh <[email protected]>

---------

Signed-off-by: Tipu_Singh <[email protected]>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 4, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
9 Security Hotspots
E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (2)

303-313: Decouple requestSigner from Prisma SignerOption and align with domain type

requestSigner.method is typed as SignerOption from @prisma/client, while there is also a domain-level SignerOption interface in apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts with a different shape. This both couples the DTO to the persistence layer and reuses the same name for different concepts, which is confusing.

Prefer a DTO/domain type here (e.g., a method enum or the domain “method” type) instead of importing from Prisma, so the API contract is independent of DB details and naming collisions are avoided.


345-356: Use only @ApiHideProperty on _oneOfCheck to keep it internal

You’ve added @ApiHideProperty() (good) but left @ApiPropertyOptional on _oneOfCheck. Keeping the Swagger decorator is unnecessary and risks conflicting metadata for a field that should remain internal.

You can safely drop @ApiPropertyOptional and rely solely on @ApiHideProperty plus the comment above for documentation:

-  @ApiHideProperty()
-  @ApiPropertyOptional({
-    description: 'Internal: ensures exactly one of dcql or presentationExchange is present'
-  })
+  @ApiHideProperty()
   @Validate(OnlyOneOfConstraint, ['dcql', 'presentationExchange'])

This preserves validation behavior while ensuring _oneOfCheck does not appear in the OpenAPI schema.

🧹 Nitpick comments (3)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (2)

1-6: Consolidate @nestjs/swagger imports to a single statement

@nestjs/swagger is imported twice (lines 2 and 5). You can merge them into one import for clarity and to satisfy the Sonar warning.

-import { ApiHideProperty, ApiPropertyOptional } from '@nestjs/swagger';
-/* eslint-disable camelcase */
-import { ApiProperty } from '@nestjs/swagger';
+import { ApiHideProperty, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+/* eslint-disable camelcase */

333-343: Tighten validation for expectedOrigins as an array of strings

expectedOrigins is annotated only with @IsOptional(). If this is intended to be string[], consider adding explicit array + element validation for safer inputs:

-  @IsOptional()
-  expectedOrigins: string[];
+  @IsOptional()
+  @IsArray()
+  @IsString({ each: true })
+  expectedOrigins: string[];

This will ensure the field is actually an array of strings when provided.

libs/enum/src/enum.ts (1)

275-339: Unify x5c key/status enums with existing patterns and normalize status strings

The new enums look conceptually sound, but a couple of details are worth tightening:

  • x5cKeyType duplicates values already present in KeyType (Ed25519, P256). Unless there is a strong reason to keep them separate, consider reusing KeyType (or a narrowed union of it) instead of introducing a parallel enum that can drift over time.
  • x5cRecordStatus mixes capitalization and spaces in its string values ('Active', 'Pending activation', 'In Active'). This is inconsistent with most other enums here that use machine‑friendly tokens (e.g., kebab/snake case, no spaces). Consider normalizing the values (e.g., 'active' | 'pending_activation' | 'inactive') and deriving user‑facing labels at the UI layer.

These are non‑blocking but will reduce surprises around comparisons, persistence, and future extensions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9477f1 and 691aee9.

📒 Files selected for processing (2)
  • apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1 hunks)
  • libs/enum/src/enum.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1)
apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts (1)
  • SignerOption (16-20)
🪛 GitHub Check: SonarCloud Code Analysis
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts

[warning] 2-2: '@nestjs/swagger' imported multiple times.

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

🔇 Additional comments (1)
apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts (1)

80-272: PEX/DCQL DTO modeling and validation look consistent

The PEX and DCQL DTOs are well-structured: required/optional fields align with examples, array and nested validations are wired correctly, and the shapes match the expected OID4VC/DCQL structures. No changes needed here from this file’s perspective.

Comment on lines +341 to +344
export declare enum HandshakeProtocol {
Connections = 'https://didcomm.org/connections/1.0',
DidExchange = 'https://didcomm.org/didexchange/1.0'
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all definitions and usages of HandshakeProtocol
rg -n "HandshakeProtocol" -C3

Repository: credebl/platform

Length of output: 2065


🏁 Script executed:

#!/bin/bash
# Look for runtime access patterns of HandshakeProtocol enum members
rg -n "HandshakeProtocol\." -C2

Repository: credebl/platform

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for validation or factory patterns that might use HandshakeProtocol values
rg -n "Connections|DidExchange" -C2 --type ts

Repository: credebl/platform

Length of output: 31901


export declare enum HandshakeProtocol will not emit a runtime enum (likely runtime bug)

Using declare here makes HandshakeProtocol ambient-only: TypeScript will not emit any JS for it, but code that imports and uses HandshakeProtocol.Connections / HandshakeProtocol.DidExchange will still reference the enum at runtime, yielding undefined and causing failures.

In this source file you almost certainly want a normal exported enum:

-export declare enum HandshakeProtocol {
+export enum HandshakeProtocol {
   Connections = 'https://didcomm.org/connections/1.0',
   DidExchange = 'https://didcomm.org/didexchange/1.0'
 }

Additionally, there is a duplicate definition of HandshakeProtocol in apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts that also uses export declare enum. Remove this duplicate and use only the definition in libs/enum/src/enum.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export declare enum HandshakeProtocol {
Connections = 'https://didcomm.org/connections/1.0',
DidExchange = 'https://didcomm.org/didexchange/1.0'
}
export enum HandshakeProtocol {
Connections = 'https://didcomm.org/connections/1.0',
DidExchange = 'https://didcomm.org/didexchange/1.0'
}
🤖 Prompt for AI Agents
In libs/enum/src/enum.ts around lines 341 to 344, the enum is declared with
"export declare enum HandshakeProtocol" which prevents TypeScript from emitting
runtime code and will make HandshakeProtocol.Connections/DidExchange undefined
at runtime; change it to a normal exported enum (remove "declare") so the enum
is emitted into JS, and delete the duplicate enum definition in
apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts (or replace its
imports to reference libs/enum) so there is a single canonical runtime-emitted
HandshakeProtocol definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants