Skip to content

Commit 3eac788

Browse files
committed
feat: added webhook
Signed-off-by: Tipu_Singh <[email protected]>
1 parent 469e080 commit 3eac788

File tree

8 files changed

+240
-164
lines changed

8 files changed

+240
-164
lines changed

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

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,10 @@ import { ApiProperty } from '@nestjs/swagger';
22
import { IsArray, IsObject, IsString } from 'class-validator';
33

44
export class CredentialOfferPayloadDto {
5-
@ApiProperty()
6-
@IsString()
7-
// eslint-disable-next-line camelcase
8-
credential_issuer!: string;
9-
105
@ApiProperty({ type: [String] })
116
@IsArray()
127
// eslint-disable-next-line camelcase
138
credential_configuration_ids!: string[];
14-
15-
@ApiProperty({ type: 'object', additionalProperties: true })
16-
@IsObject()
17-
grants!: Record<string, unknown>;
18-
19-
@ApiProperty({ type: [Object] })
20-
@IsArray()
21-
credentials!: Record<string, unknown>[];
229
}
2310

2411
export class IssuanceMetadataDto {
@@ -40,11 +27,48 @@ export class OidcIssueCredentialDto {
4027
@IsString()
4128
credentialOfferId!: string;
4229

30+
@ApiProperty({ type: [Object] })
31+
@IsArray()
32+
issuedCredentials!: Record<string, unknown>[];
33+
34+
@ApiProperty({ type: CredentialOfferPayloadDto })
35+
@IsObject()
36+
credentialOfferPayload!: CredentialOfferPayloadDto;
37+
4338
@ApiProperty()
4439
@IsString()
4540
state!: string;
4641

42+
@ApiProperty()
43+
@IsString()
44+
createdAt!: string;
45+
46+
@ApiProperty()
47+
@IsString()
48+
updatedAt!: string;
49+
4750
@ApiProperty()
4851
@IsString()
4952
contextCorrelationId!: string;
5053
}
54+
55+
/**
56+
* Utility: return only credential_configuration_ids from a webhook payload
57+
*/
58+
export function extractCredentialConfigurationIds(payload: Partial<OidcIssueCredentialDto>): string[] {
59+
const cfg = payload?.credentialOfferPayload?.credential_configuration_ids;
60+
return Array.isArray(cfg) ? cfg : [];
61+
}
62+
63+
export function sanitizeOidcIssueCredentialDto(
64+
payload: Partial<OidcIssueCredentialDto>
65+
): Partial<OidcIssueCredentialDto> {
66+
const ids = extractCredentialConfigurationIds(payload);
67+
return {
68+
...payload,
69+
credentialOfferPayload: {
70+
// eslint-disable-next-line camelcase
71+
credential_configuration_ids: ids
72+
}
73+
};
74+
}

apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler
4545
import { user } from '@prisma/client';
4646
import { IssuerCreationDto, IssuerUpdationDto } from './dtos/oid4vc-issuer.dto';
4747
import { CreateCredentialTemplateDto, UpdateCredentialTemplateDto } from './dtos/oid4vc-issuer-template.dto';
48-
import { OidcIssueCredentialDto } from './dtos/oid4vc-credential-wh.dto';
48+
import { OidcIssueCredentialDto, sanitizeOidcIssueCredentialDto } from './dtos/oid4vc-credential-wh.dto';
4949
import { Oid4vcIssuanceService } from './oid4vc-issuance.service';
5050
import {
5151
CreateCredentialOfferD2ADto,
@@ -630,7 +630,7 @@ export class Oid4vcIssuanceController {
630630
@Param('id') id: string,
631631
@Res() res: Response
632632
): Promise<Response> {
633-
console.log('Webhook received:', JSON.stringify(oidcIssueCredentialDto, null, 2));
633+
// const sanitized = sanitizeOidcIssueCredentialDto(oidcIssueCredentialDto);
634634
const getCredentialDetails = await this.oid4vcIssuanceService.oidcIssueCredentialWebhook(
635635
oidcIssueCredentialDto,
636636
id

apps/oid4vc-issuance/interfaces/oid4vc-wh-interfaces.ts

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,14 @@
1-
export interface CredentialOfferPayload {
2-
credential_issuer: string;
3-
credential_configuration_ids: string[];
4-
grants: Record<string, unknown>;
5-
credentials: Record<string, unknown>[];
6-
}
7-
8-
export interface IssuanceMetadata {
9-
issuerDid: string;
10-
credentials: Record<string, unknown>[];
11-
}
12-
13-
export interface OidcIssueCredential {
14-
_tags: Record<string, unknown>;
15-
metadata: Record<string, unknown>;
16-
issuedCredentials: Record<string, unknown>[];
17-
id: string;
18-
createdAt: string; // ISO date string
19-
issuerId: string;
20-
userPin: string;
21-
preAuthorizedCode: string;
22-
credentialOfferUri: string;
23-
credentialOfferId: string;
24-
credentialOfferPayload: CredentialOfferPayload;
25-
issuanceMetadata: IssuanceMetadata;
26-
state: string;
27-
updatedAt: string; // ISO date string
28-
contextCorrelationId: string;
29-
}
30-
31-
export interface CredentialOfferWebhookPayload {
32-
credentialOfferId: string;
1+
export interface Oid4vcCredentialOfferWebhookPayload {
332
id: string;
34-
State: string;
35-
contextCorrelationId: string;
3+
credentialOfferId?: string;
4+
issuedCredentials?: Record<string, unknown>[];
5+
createdAt?: string;
6+
updatedAt?: string;
7+
credentialOfferPayload?: {
8+
credential_configuration_ids?: string[];
9+
};
10+
state?: string;
11+
contextCorrelationId?: string;
3612
}
3713

3814
export interface CredentialPayload {

apps/oid4vc-issuance/src/oid4vc-issuance.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
GetAllCredentialOffer
1111
} from '../interfaces/oid4vc-issuer-sessions.interfaces';
1212
import { CreateCredentialTemplate, UpdateCredentialTemplate } from '../interfaces/oid4vc-template.interfaces';
13-
import { CredentialOfferWebhookPayload } from '../interfaces/oid4vc-wh-interfaces';
13+
import { Oid4vcCredentialOfferWebhookPayload } from '../interfaces/oid4vc-wh-interfaces';
1414

1515
@Controller()
1616
export class Oid4vcIssuanceController {
@@ -176,7 +176,7 @@ export class Oid4vcIssuanceController {
176176
}
177177

178178
@MessagePattern({ cmd: 'webhook-oid4vc-issue-credential' })
179-
async oidcIssueCredentialWebhook(payload: CredentialOfferWebhookPayload): Promise<object> {
179+
async oidcIssueCredentialWebhook(payload: Oid4vcCredentialOfferWebhookPayload): Promise<object> {
180180
return this.oid4vcIssuanceService.storeOidcCredentialWebhook(payload);
181181
}
182182
}

apps/oid4vc-issuance/src/oid4vc-issuance.repository.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,33 +65,57 @@ export class Oid4vcIssuanceRepository {
6565
}
6666
}
6767

68-
async storeOidcCredentialDetails(credentialPayload): Promise<object> {
68+
async storeOidcCredentialDetails(
69+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
70+
credentialPayload:
71+
| {
72+
id: string;
73+
credentialOfferId?: string;
74+
state?: string;
75+
contextCorrelationId?: string;
76+
credentialConfigurationIds?: string[];
77+
issuedCredentials?: string[];
78+
}
79+
| any,
80+
orgId: string
81+
): Promise<object> {
6982
try {
70-
const { credentialOfferId, state, offerId, contextCorrelationId, orgId } = credentialPayload;
83+
const payload = credentialPayload?.oidcIssueCredentialDto ?? credentialPayload ?? {};
84+
const {
85+
credentialOfferId,
86+
state,
87+
id: issuanceSessionId,
88+
contextCorrelationId,
89+
credentialOfferPayload,
90+
issuedCredentials
91+
} = payload;
92+
7193
const credentialDetails = await this.prisma.oid4vc_credentials.upsert({
7294
where: {
73-
offerId
95+
issuanceSessionId
7496
},
7597
update: {
7698
lastChangedBy: orgId,
77-
credentialOfferId,
78-
contextCorrelationId,
79-
state
99+
state,
100+
credentialConfigurationIds: credentialOfferPayload.credential_configuration_ids ?? [],
101+
...(issuedCredentials !== undefined ? { issuedCredentials } : {})
80102
},
81103
create: {
82104
lastChangedBy: orgId,
83105
createdBy: orgId,
84106
state,
85107
orgId,
86108
credentialOfferId,
87-
offerId,
88-
contextCorrelationId
109+
contextCorrelationId,
110+
issuanceSessionId,
111+
credentialConfigurationIds: credentialOfferPayload.credential_configuration_ids ?? [],
112+
...(issuedCredentials !== undefined ? { issuedCredentials } : {})
89113
}
90114
});
91115

92116
return credentialDetails;
93117
} catch (error) {
94-
this.logger.error(`Error in get storeOidcCredentialDetails: ${error.message} `);
118+
this.logger.error(`Error in storeOidcCredentialDetails in issuance repository: ${error.message} `);
95119
throw error;
96120
}
97121
}

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import { x5cKeyType } from '@credebl/enum/enum';
6161
import { instanceToPlain, plainToInstance } from 'class-transformer';
6262
import { X509CertificateRecord } from '@credebl/common/interfaces/x509.interface';
63+
import { Oid4vcCredentialOfferWebhookPayload } from '../interfaces/oid4vc-wh-interfaces';
6364

6465
type CredentialDisplayItem = {
6566
logo?: { uri: string; alt_text?: string };
@@ -953,33 +954,60 @@ export class Oid4vcIssuanceService {
953954
return agentDetails.agentEndPoint;
954955
}
955956

956-
async storeOidcCredentialWebhook(CredentialOfferWebhookPayload): Promise<object> {
957+
async storeOidcCredentialWebhook(
958+
CredentialOfferWebhookPayload: Oid4vcCredentialOfferWebhookPayload
959+
): Promise<object> {
957960
try {
958-
console.log('Storing OID4VC Credential Webhook:', CredentialOfferWebhookPayload);
959-
const { credentialOfferId, state, id, contextCorrelationId } = CredentialOfferWebhookPayload;
961+
// pick fields
962+
const {
963+
credentialOfferId,
964+
state,
965+
id: issuanceSessionId,
966+
contextCorrelationId,
967+
credentialOfferPayload,
968+
issuedCredentials
969+
} = CredentialOfferWebhookPayload ?? {};
970+
971+
// ensure we only store credential_configuration_ids in the payload for logging and storage
972+
const cfgIds: string[] = Array.isArray(credentialOfferPayload?.credential_configuration_ids)
973+
? credentialOfferPayload.credential_configuration_ids
974+
: [];
975+
976+
// convert issuedCredentials to string[] when schema expects string[]
977+
const issuedCredentialsArr: string[] | undefined =
978+
Array.isArray(issuedCredentials) && 0 < issuedCredentials.length
979+
? issuedCredentials.map((c: any) => ('string' === typeof c ? c : JSON.stringify(c)))
980+
: issuedCredentials && Array.isArray(issuedCredentials) && 0 === issuedCredentials.length
981+
? []
982+
: undefined;
983+
984+
const sanitized = {
985+
...CredentialOfferWebhookPayload,
986+
credentialOfferPayload: {
987+
credential_configuration_ids: cfgIds
988+
}
989+
};
990+
991+
console.log('Storing OID4VC Credential Webhook:', JSON.stringify(sanitized, null, 2));
992+
993+
// resolve orgId (unchanged logic)
960994
let orgId: string;
961995
if ('default' !== contextCorrelationId) {
962996
const getOrganizationId = await this.oid4vcIssuanceRepository.getOrganizationByTenantId(contextCorrelationId);
963997
orgId = getOrganizationId?.orgId;
964998
} else {
965-
orgId = id;
999+
orgId = issuanceSessionId;
9661000
}
9671001

968-
const credentialPayload = {
969-
orgId,
970-
offerId: id,
971-
credentialOfferId,
972-
state,
973-
contextCorrelationId
974-
};
975-
976-
const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(credentialPayload);
1002+
// hand off to repository for persistence (repository will perform the upsert)
1003+
const agentDetails = await this.oid4vcIssuanceRepository.storeOidcCredentialDetails(
1004+
CredentialOfferWebhookPayload,
1005+
orgId
1006+
);
9771007
return agentDetails;
9781008
} catch (error) {
979-
this.logger.error(
980-
`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`
981-
);
982-
throw new RpcException(error.response ? error.response : error);
1009+
this.logger.error(`[storeOidcCredentialWebhook] - error: ${JSON.stringify(error)}`);
1010+
throw error;
9831011
}
9841012
}
9851013
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `offerId` on the `oid4vc_credentials` table. All the data in the column will be lost.
5+
- A unique constraint covering the columns `[issuanceSessionId]` on the table `oid4vc_credentials` will be added. If there are existing duplicate values, this will fail.
6+
- Added the required column `issuanceSessionId` to the `oid4vc_credentials` table without a default value. This is not possible if the table is not empty.
7+
8+
*/
9+
-- DropIndex
10+
DROP INDEX "oid4vc_credentials_offerId_key";
11+
12+
-- AlterTable
13+
ALTER TABLE "oid4vc_credentials" DROP COLUMN "offerId",
14+
ADD COLUMN "credentialConfigurationIds" TEXT[],
15+
ADD COLUMN "issuanceSessionId" TEXT NOT NULL,
16+
ADD COLUMN "issuedCredentials" TEXT[];
17+
18+
-- CreateIndex
19+
CREATE UNIQUE INDEX "oid4vc_credentials_issuanceSessionId_key" ON "oid4vc_credentials"("issuanceSessionId");
20+
21+
-- CreateIndex
22+
CREATE INDEX "oid4vc_credentials_credentialConfigurationIds_idx" ON "oid4vc_credentials" USING GIN ("credentialConfigurationIds");

0 commit comments

Comments
 (0)