From b70ded7db8447248802302bfd9a9f23256c46c39 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Thu, 6 Nov 2025 13:47:53 +0530 Subject: [PATCH 1/3] feat: verification webhook implementation Signed-off-by: Tipu_Singh --- .../dtos/oid4vc-issuer-template.dto.ts | 13 ++++--- .../dtos/oid4vp-presentation-wh.dto.ts | 27 +++++++++++++ .../oid4vc-verification.controller.ts | 34 +++++++++++++++-- .../oid4vc-verification.module.ts | 2 +- .../oid4vc-verification.service.ts | 11 ++++++ .../helpers/credential-sessions.builder.ts | 2 +- apps/oid4vc-issuance/src/main.ts | 2 +- .../src/oid4vc-issuance.service.ts | 9 ----- ...oid4vp-verification-sessions.interfaces.ts | 8 ++++ .../src/oid4vc-verification.controller.ts | 9 +++++ .../src/oid4vc-verification.repository.ts | 38 +++++++++++++++++++ .../src/oid4vc-verification.service.ts | 28 ++++++++++++-- libs/common/src/common.constant.ts | 2 +- libs/common/src/response-messages/index.ts | 20 ++++++++++ .../migration.sql | 21 ++++++++++ libs/prisma-service/prisma/schema.prisma | 36 +++++++++++++----- 16 files changed, 226 insertions(+), 36 deletions(-) create mode 100644 apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts create mode 100644 apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts create mode 100644 libs/prisma-service/prisma/migrations/20251105180717_created_table_oid4vp_presentation/migration.sql diff --git a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts index 5397d6524..ad8d175ba 100644 --- a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts +++ b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts @@ -10,7 +10,9 @@ import { IsArray, ValidateIf, IsEmpty, - ArrayNotEmpty + ArrayNotEmpty, + IsDefined, + NotEquals } from 'class-validator'; import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath, PartialType } from '@nestjs/swagger'; import { Type } from 'class-transformer'; @@ -216,12 +218,11 @@ export class CreateCredentialTemplateDto { @IsString() description?: string; - @ApiProperty({ - description: 'Signer option (did or x509)', - enum: SignerOption, - example: SignerOption.DID - }) + @ApiProperty({ enum: SignerOption, description: 'Signer option type' }) @IsEnum(SignerOption) + @ValidateIf((o) => o.format === CredentialFormat.Mdoc) + @IsDefined({ message: 'signerOption is required when format is Mdoc' }) + @NotEquals(SignerOption.DID, { message: 'signerOption must NOT be DID when format is Mdoc' }) signerOption!: SignerOption; @ApiProperty({ enum: CredentialFormat, description: 'Credential format type' }) diff --git a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts new file mode 100644 index 000000000..cc8dcc4e6 --- /dev/null +++ b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vp-presentation-wh.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class Oid4vpPresentationWhDto { + @ApiProperty() + @IsString() + id!: string; + + @ApiProperty() + @IsString() + state!: string; + + @ApiProperty() + @IsString() + authorizationRequestId!: string; + + @ApiProperty() + @IsString() + createdAt!: string; + + @ApiProperty() + @IsString() + updatedAt!: string; + + @ApiProperty() + @IsString() + contextCorrelationId!: string; +} diff --git a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts index 6c9edfa2b..9ca3071ec 100644 --- a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts +++ b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts @@ -27,14 +27,15 @@ import { ApiBearerAuth, ApiForbiddenResponse, ApiUnauthorizedResponse, - ApiQuery + ApiQuery, + ApiExcludeEndpoint } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { Response } from 'express'; -import { IResponse } from '@credebl/common/interfaces/response.interface'; +import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { User } from '../authz/decorators/user.decorator'; import { ResponseMessages } from '@credebl/common/response-messages'; import { Roles } from '../authz/decorators/roles.decorator'; @@ -46,6 +47,7 @@ import { user } from '@prisma/client'; import { Oid4vcVerificationService } from './oid4vc-verification.service'; import { CreateVerifierDto, UpdateVerifierDto } from './dtos/oid4vc-verifier.dto'; import { PresentationRequestDto, VerificationPresentationQueryDto } from './dtos/oid4vc-verifier-presentation.dto'; +import { Oid4vpPresentationWhDto } from '../oid4vc-issuance/dtos/oid4vp-presentation-wh.dto'; @Controller() @UseFilters(CustomExceptionFilter) @ApiTags('OID4VP') @@ -296,7 +298,7 @@ export class Oid4vcVerificationController { const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.oid4vp.success.create, + message: ResponseMessages.oid4vpSession.success.create, data: presentation }; return res.status(HttpStatus.CREATED).json(finalResponse); @@ -408,4 +410,30 @@ export class Oid4vcVerificationController { throw new BadRequestException(error.message || 'Failed to fetch verifier presentation response details.'); } } + /** + * Catch issue credential webhook responses + * @param oid4vpPresentationWhDto The details of the oid4vp presentation webhook + * @param id The ID of the organization + * @param res The response object + * @returns The details of the oid4vp presentation webhook + */ + @Post('wh/:id/openid4vc-verification') + @ApiExcludeEndpoint() + @ApiOperation({ + summary: 'Catch OID4VP presentation states', + description: 'Handles webhook responses for OID4VP presentation states.' + }) + async storePresentationWebhook( + @Body() oid4vpPresentationWhDto: Oid4vpPresentationWhDto, + @Param('id') id: string, + @Res() res: Response + ): Promise { + await this.oid4vcVerificationService.oid4vpPresentationWebhook(oid4vpPresentationWhDto, id); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.issuance.success.create, + data: [] + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } } diff --git a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.module.ts b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.module.ts index 4190f9708..b4cbf3344 100644 --- a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.module.ts +++ b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.module.ts @@ -16,7 +16,7 @@ import { LoggerModule } from '@credebl/logger'; { name: 'NATS_CLIENT', transport: Transport.NATS, - options: getNatsOptions(CommonConstants.ISSUANCE_SERVICE, process.env.API_GATEWAY_NKEY_SEED) + options: getNatsOptions(CommonConstants.OIDC4VC_VERIFICATION_SERVICE, process.env.API_GATEWAY_NKEY_SEED) } ]) ], diff --git a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts index a2bd17b30..3a48e9fd4 100644 --- a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts +++ b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.service.ts @@ -6,6 +6,7 @@ import { BaseService } from 'libs/service/base.service'; import { oid4vp_verifier, user } from '@prisma/client'; import { CreateVerifierDto, UpdateVerifierDto } from './dtos/oid4vc-verifier.dto'; import { VerificationPresentationQueryDto } from './dtos/oid4vc-verifier-presentation.dto'; +import { Oid4vpPresentationWhDto } from '../oid4vc-issuance/dtos/oid4vp-presentation-wh.dto'; @Injectable() export class Oid4vcVerificationService extends BaseService { @@ -81,4 +82,14 @@ export class Oid4vcVerificationService extends BaseService { ); return this.natsClient.sendNatsMessage(this.oid4vpProxy, 'oid4vp-verification-session-create', payload); } + + oid4vpPresentationWebhook( + oid4vpPresentationWhDto: Oid4vpPresentationWhDto, + id: string + ): Promise<{ + response: object; + }> { + const payload = { oid4vpPresentationWhDto, id }; + return this.natsClient.sendNats(this.oid4vpProxy, 'webhook-oid4vp-presentation', payload); + } } diff --git a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts index 089052c44..ae5eae3d9 100644 --- a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts +++ b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts @@ -349,7 +349,7 @@ function buildMdocCredential( ) { throw new UnprocessableEntityException(`${ResponseMessages.oidcIssuerSession.error.missingValidityInfo}`); } - + //TODO: add validation, signerOptions must be x5c for mdoc. const certificateDetail = activeCertificateDetails.find((x) => x.certificateBase64 === signerOptions[0].x5c[0]); const validationResult = validateCredentialDatesInCertificateWindow( credentialRequest.validityInfo, diff --git a/apps/oid4vc-issuance/src/main.ts b/apps/oid4vc-issuance/src/main.ts index a76e0de0f..be0701c3d 100644 --- a/apps/oid4vc-issuance/src/main.ts +++ b/apps/oid4vc-issuance/src/main.ts @@ -12,7 +12,7 @@ const logger = new Logger(); async function bootstrap(): Promise { const app = await NestFactory.createMicroservice(Oid4vcIssuanceModule, { transport: Transport.NATS, - options: getNatsOptions(CommonConstants.OIDC4VC_ISSUANCE_SERVICE, process.env.ISSUANCE_NKEY_SEED) + options: getNatsOptions(CommonConstants.OIDC4VC_ISSUANCE_SERVICE, process.env.OIDC4VC_ISSUANCE_NKEY_SEED) }); app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); diff --git a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts index 921575f02..52743a8ae 100644 --- a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts +++ b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts @@ -694,7 +694,6 @@ export class Oid4vcIssuanceService { return updateCredentialOfferOnAgent.response; } catch (error) { - this.logger.error(`[createOidcCredentialOffer] - error: ${JSON.stringify(error)}`); throw new RpcException(error.response ?? error); } } @@ -967,13 +966,9 @@ export class Oid4vcIssuanceService { credentialOfferPayload, issuedCredentials } = CredentialOfferWebhookPayload ?? {}; - - // ensure we only store credential_configuration_ids in the payload for logging and storage const cfgIds: string[] = Array.isArray(credentialOfferPayload?.credential_configuration_ids) ? credentialOfferPayload.credential_configuration_ids : []; - - // convert issuedCredentials to string[] when schema expects string[] const issuedCredentialsArr: string[] | undefined = Array.isArray(issuedCredentials) && 0 < issuedCredentials.length ? issuedCredentials.map((c: any) => ('string' === typeof c ? c : JSON.stringify(c))) @@ -989,8 +984,6 @@ export class Oid4vcIssuanceService { }; console.log('Storing OID4VC Credential Webhook:', JSON.stringify(sanitized, null, 2)); - - // resolve orgId (unchanged logic) let orgId: string; if ('default' !== contextCorrelationId) { const getOrganizationId = await this.oid4vcIssuanceRepository.getOrganizationByTenantId(contextCorrelationId); @@ -998,8 +991,6 @@ export class Oid4vcIssuanceService { } else { orgId = issuanceSessionId; } - - // hand off to repository for persistence (repository will perform the upsert) const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails( CredentialOfferWebhookPayload, orgId diff --git a/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts b/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts new file mode 100644 index 000000000..86b447c96 --- /dev/null +++ b/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts @@ -0,0 +1,8 @@ +export interface Oid4vpPresentationWh { + id: string; + state: string; + createdAt: string; + updatedAt: string; + contextCorrelationId: string; + authorizationRequestId: string; +} diff --git a/apps/oid4vc-verification/src/oid4vc-verification.controller.ts b/apps/oid4vc-verification/src/oid4vc-verification.controller.ts index 69c73018d..5422e7ff8 100644 --- a/apps/oid4vc-verification/src/oid4vc-verification.controller.ts +++ b/apps/oid4vc-verification/src/oid4vc-verification.controller.ts @@ -4,6 +4,7 @@ import { user } from '@prisma/client'; import { CreateVerifier, UpdateVerifier } from '@credebl/common/interfaces/oid4vp-verification'; import { MessagePattern } from '@nestjs/microservices'; import { VerificationSessionQuery } from '../interfaces/oid4vp-verifier.interfaces'; +import { Oid4vpPresentationWh } from '../interfaces/oid4vp-verification-sessions.interfaces'; @Controller() export class Oid4vpVerificationController { @@ -91,4 +92,12 @@ export class Oid4vpVerificationController { userDetails ); } + + @MessagePattern({ cmd: 'webhook-oid4vp-presentation' }) + async oid4vpPresentationWebhook(payload: { + oid4vpPresentationWhDto: Oid4vpPresentationWh; + id: string; + }): Promise { + return this.oid4vpVerificationService.oid4vpPresentationWebhook(payload.oid4vpPresentationWhDto, payload.id); + } } diff --git a/apps/oid4vc-verification/src/oid4vc-verification.repository.ts b/apps/oid4vc-verification/src/oid4vc-verification.repository.ts index d49f342bf..3c0a0bb20 100644 --- a/apps/oid4vc-verification/src/oid4vc-verification.repository.ts +++ b/apps/oid4vc-verification/src/oid4vc-verification.repository.ts @@ -5,6 +5,7 @@ import { oid4vp_verifier, org_agents } from '@prisma/client'; import { PrismaService } from '@credebl/prisma-service'; import { ResponseMessages } from '@credebl/common/response-messages'; import { OrgAgent } from '../interfaces/oid4vp-verifier.interfaces'; +import { Oid4vpPresentationWh } from '../interfaces/oid4vp-verification-sessions.interfaces'; @Injectable() export class Oid4vpRepository { @@ -188,4 +189,41 @@ export class Oid4vpRepository { throw error; } } + + async storeOid4vpPresentationDetails( + oid4vpPresentationPayload: Oid4vpPresentationWh, + orgId: string + ): Promise { + try { + const { + state, + id: verificationSessionId, + contextCorrelationId, + authorizationRequestId + } = oid4vpPresentationPayload; + const credentialDetails = await this.prisma.oid4vp_presentations.upsert({ + where: { + verificationSessionId + }, + update: { + lastChangedBy: orgId, + state + }, + create: { + lastChangedBy: orgId, + createdBy: orgId, + state, + orgId, + contextCorrelationId, + verificationSessionId, + presentationId: authorizationRequestId + } + }); + + return credentialDetails; + } catch (error) { + this.logger.error(`Error in storeOid4vpPresentationDetails in oid4vp-presentation repository: ${error.message} `); + throw error; + } + } } diff --git a/apps/oid4vc-verification/src/oid4vc-verification.service.ts b/apps/oid4vc-verification/src/oid4vc-verification.service.ts index 88f07e616..1b5566a3b 100644 --- a/apps/oid4vc-verification/src/oid4vc-verification.service.ts +++ b/apps/oid4vc-verification/src/oid4vc-verification.service.ts @@ -27,6 +27,7 @@ import { buildUrlWithQuery } from '@credebl/common/cast.helper'; import { VerificationSessionQuery } from '../interfaces/oid4vp-verifier.interfaces'; import { RequestSignerMethod } from '@credebl/enum/enum'; import { BaseService } from 'libs/service/base.service'; +import { Oid4vpPresentationWh } from '../interfaces/oid4vp-verification-sessions.interfaces'; @Injectable() export class Oid4vpVerificationService extends BaseService { @@ -282,10 +283,11 @@ export class Oid4vpVerificationService extends BaseService { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } const { agentEndPoint, id } = agentDetails; - - const url = getAgentUrl(agentEndPoint, CommonConstants.OIDC_VERIFIER_SESSION_GET_BY_ID, verificationSessionId); - this.logger.debug(`[getVerificationSessionResponse] calling agent URL=${url}`); - + const url = getAgentUrl( + agentEndPoint, + CommonConstants.OIDC_VERIFIER_SESSION_RESPONSE_GET_BY_ID, + verificationSessionId + ); const verifiers = await await this._getOid4vpVerifierSession(url, orgId); if (!verifiers || 0 === verifiers.length) { throw new NotFoundException(ResponseMessages.oid4vp.error.notFound); @@ -298,6 +300,24 @@ export class Oid4vpVerificationService extends BaseService { } } + async oid4vpPresentationWebhook(oid4vpPresentation: Oid4vpPresentationWh, id: string): Promise { + try { + const { contextCorrelationId } = oid4vpPresentation ?? {}; + let orgId: string; + if ('default' !== contextCorrelationId) { + const getOrganizationId = await this.oid4vpRepository.getOrganizationByTenantId(contextCorrelationId); + orgId = getOrganizationId?.orgId; + } else { + orgId = id; + } + const agentDetails = await this.oid4vpRepository.storeOid4vpPresentationDetails(oid4vpPresentation, orgId); + return agentDetails; + } catch (error) { + this.logger.error(`[storeOidcCredentialWebhook] - error: ${JSON.stringify(error)}`); + throw error; + } + } + async _createOid4vpVerifier(verifierDetails: CreateVerifier, url: string, orgId: string): Promise { this.logger.debug(`[_createOid4vpVerifier] sending NATS message for orgId=${orgId}`); try { diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index ab3ff8684..e4e8ef18e 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -133,7 +133,7 @@ export enum CommonConstants { URL_OIDC_VERIFIER_GET = '/openid4vc/verifier/#', URL_OIDC_VERIFIER_SESSION_GET_BY_ID = '/openid4vc/verification-sessions/#', URL_OIDC_VERIFIER_SESSION_GET_BY_QUERY = '/openid4vc/verification-sessions', - URL_OIDC_VERIFIER_SESSION_RESPONSE_GET_BY_ID = '/openid4vc/verification-sessions/response#', + URL_OIDC_VERIFIER_SESSION_RESPONSE_GET_BY_ID = '/openid4vc/verification-sessions/response/#', URL_OID4VP_VERIFICATION_SESSION = '/openid4vc/verification-sessions/create-presentation-request', //X509 agent API URLs diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 450e9f7b0..d7f691bf2 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -576,6 +576,26 @@ export const ResponseMessages = { deleteTemplate: 'Error while deleting template.' } }, + oid4vpSession: { + success: { + create: 'OID4VP session verifier created successfully.', + update: 'OID4V session verifier updated successfully.', + delete: 'OID4VP session verifier deleted successfully.', + fetch: 'OID4VP session verifier(s) fetched successfully.', + getById: 'OID4VP session verifier details fetched successfully.' + }, + error: { + notFound: 'OID4VP session verifier not found.', + invalidId: 'Invalid OID4VP session verifier ID.', + createFailed: 'Failed to create OID4VP session verifier.', + updateFailed: 'Failed to update OID4VP session verifier.', + deleteFailed: 'Failed to delete OID4VP session verifier.', + notFoundIssuerDisplay: 'Issuer display not found.', + notFoundIssuerDetails: 'Issuer details not found.', + verifierIdAlreadyExists: 'Verifier ID already exists for this verifier.', + deleteTemplate: 'Error while deleting template.' + } + }, x509: { success: { create: 'x509 certificate created successfully', diff --git a/libs/prisma-service/prisma/migrations/20251105180717_created_table_oid4vp_presentation/migration.sql b/libs/prisma-service/prisma/migrations/20251105180717_created_table_oid4vp_presentation/migration.sql new file mode 100644 index 000000000..1ba8f23c5 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20251105180717_created_table_oid4vp_presentation/migration.sql @@ -0,0 +1,21 @@ +-- CreateTable +CREATE TABLE "oid4vp_presentations" ( + "id" UUID NOT NULL, + "orgId" UUID NOT NULL, + "verificationSessionId" TEXT NOT NULL, + "presentationId" TEXT NOT NULL, + "state" TEXT NOT NULL, + "contextCorrelationId" TEXT NOT NULL, + "createdBy" UUID NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" UUID NOT NULL, + + CONSTRAINT "oid4vp_presentations_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "oid4vp_presentations_verificationSessionId_key" ON "oid4vp_presentations"("verificationSessionId"); + +-- AddForeignKey +ALTER TABLE "oid4vp_presentations" ADD CONSTRAINT "oid4vp_presentations_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "organisation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 1a95f1e6a..df567d673 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -152,6 +152,7 @@ model organisation { credential_definition credential_definition[] file_upload file_upload[] oid4vc_credentials oid4vc_credentials[] + oid4vp_presentations oid4vp_presentations[] } model org_invitations { @@ -588,16 +589,16 @@ model oidc_issuer { } model oid4vc_credentials { - id String @id @default(uuid()) @db.Uuid - orgId String @db.Uuid - issuanceSessionId String @unique - credentialOfferId String - state String - contextCorrelationId String - createdBy String @db.Uuid - createDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy String @db.Uuid + id String @id @default(uuid()) @db.Uuid + orgId String @db.Uuid + issuanceSessionId String @unique + credentialOfferId String + state String + contextCorrelationId String + createdBy String @db.Uuid + createDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy String @db.Uuid credentialConfigurationIds String[] issuedCredentials String[] @@ -606,6 +607,20 @@ model oid4vc_credentials { @@index([credentialConfigurationIds], type: Gin) } +model oid4vp_presentations { + id String @id @default(uuid()) @db.Uuid + orgId String @db.Uuid + verificationSessionId String @unique + presentationId String + state String + contextCorrelationId String + createdBy String @db.Uuid + createDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy String @db.Uuid + organisation organisation @relation(fields: [orgId], references: [id]) +} + enum SignerOption { DID X509_P256 @@ -661,5 +676,6 @@ model oid4vp_verifier { lastChangedBy String @default("1") orgAgentId String @db.Uuid orgAgent org_agents @relation(fields: [orgAgentId], references: [id]) + @@index([orgAgentId]) } From b7a98f03deae2a7c88a38a820a4390f892115b6f Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Thu, 6 Nov 2025 14:04:45 +0530 Subject: [PATCH 2/3] fix: batch size and nats config Signed-off-by: Tipu_Singh --- .../interfaces/oid4vc-issuance.interfaces.ts | 2 +- apps/oid4vc-issuance/src/oid4vc-issuance.service.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts b/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts index eb5a59ad9..e3ba6413e 100644 --- a/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts +++ b/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts @@ -69,7 +69,7 @@ export interface IssuerInitialConfig { authorizationServerConfigs: AuthorizationServerConfig | {}; accessTokenSignerKeyType: AccessTokenSignerKeyType; dpopSigningAlgValuesSupported: string[]; - batchCredentialIssuance: object; + batchCredentialIssuance?: object; credentialConfigurationsSupported: object; } diff --git a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts index 52743a8ae..302d5a69a 100644 --- a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts +++ b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts @@ -99,10 +99,14 @@ export class Oid4vcIssuanceService { authorizationServerConfigs: issuerCreation?.authorizationServerConfigs || undefined, accessTokenSignerKeyType, dpopSigningAlgValuesSupported, - batchCredentialIssuance: { - batchSize: batchCredentialIssuanceSize ?? batchCredentialIssuanceDefault - }, - credentialConfigurationsSupported + credentialConfigurationsSupported, + ...(batchCredentialIssuanceSize && 0 < batchCredentialIssuanceSize + ? { + batchCredentialIssuance: { + batchSize: batchCredentialIssuanceSize + } + } + : {}) }; let createdIssuer; try { From 13cf642bacaf0cc743cacc32f4f35d22f4a1ae79 Mon Sep 17 00:00:00 2001 From: Tipu_Singh Date: Thu, 6 Nov 2025 14:53:10 +0530 Subject: [PATCH 3/3] fix: review comment Signed-off-by: Tipu_Singh --- .../oid4vc-verification/oid4vc-verification.controller.ts | 2 +- .../libs/helpers/credential-sessions.builder.ts | 1 - apps/oid4vc-verification/src/oid4vc-verification.service.ts | 5 ++++- libs/common/src/response-messages/index.ts | 5 +++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts index 9ca3071ec..a6eaf6453 100644 --- a/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts +++ b/apps/api-gateway/src/oid4vc-verification/oid4vc-verification.controller.ts @@ -431,7 +431,7 @@ export class Oid4vcVerificationController { await this.oid4vcVerificationService.oid4vpPresentationWebhook(oid4vpPresentationWhDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.issuance.success.create, + message: ResponseMessages.oid4vpSession.success.webhookReceived, data: [] }; return res.status(HttpStatus.CREATED).json(finalResponse); diff --git a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts index ae5eae3d9..b2a6feff9 100644 --- a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts +++ b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts @@ -349,7 +349,6 @@ function buildMdocCredential( ) { throw new UnprocessableEntityException(`${ResponseMessages.oidcIssuerSession.error.missingValidityInfo}`); } - //TODO: add validation, signerOptions must be x5c for mdoc. const certificateDetail = activeCertificateDetails.find((x) => x.certificateBase64 === signerOptions[0].x5c[0]); const validationResult = validateCredentialDatesInCertificateWindow( credentialRequest.validityInfo, diff --git a/apps/oid4vc-verification/src/oid4vc-verification.service.ts b/apps/oid4vc-verification/src/oid4vc-verification.service.ts index 1b5566a3b..77e7f9b5c 100644 --- a/apps/oid4vc-verification/src/oid4vc-verification.service.ts +++ b/apps/oid4vc-verification/src/oid4vc-verification.service.ts @@ -306,6 +306,9 @@ export class Oid4vpVerificationService extends BaseService { let orgId: string; if ('default' !== contextCorrelationId) { const getOrganizationId = await this.oid4vpRepository.getOrganizationByTenantId(contextCorrelationId); + if (!getOrganizationId) { + throw new NotFoundException(ResponseMessages.organisation.error.notFound); + } orgId = getOrganizationId?.orgId; } else { orgId = id; @@ -313,7 +316,7 @@ export class Oid4vpVerificationService extends BaseService { const agentDetails = await this.oid4vpRepository.storeOid4vpPresentationDetails(oid4vpPresentation, orgId); return agentDetails; } catch (error) { - this.logger.error(`[storeOidcCredentialWebhook] - error: ${JSON.stringify(error)}`); + this.logger.error(`[storeOid4vpPresentationWebhook] - error: ${JSON.stringify(error)}`); throw error; } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index d7f691bf2..3d56e2450 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -579,10 +579,11 @@ export const ResponseMessages = { oid4vpSession: { success: { create: 'OID4VP session verifier created successfully.', - update: 'OID4V session verifier updated successfully.', + update: 'OID4VP session verifier updated successfully.', delete: 'OID4VP session verifier deleted successfully.', fetch: 'OID4VP session verifier(s) fetched successfully.', - getById: 'OID4VP session verifier details fetched successfully.' + getById: 'OID4VP session verifier details fetched successfully.', + webhookReceived: 'OID4VP presentation webhook stored successfully.' }, error: { notFound: 'OID4VP session verifier not found.',