Skip to content

Commit 10322f3

Browse files
authored
Merge pull request #1534 from credebl/feat/oid4vc-verification-template
Feat/oid4vc verification template
2 parents 141ed7e + 6c95f93 commit 10322f3

34 files changed

Lines changed: 2228 additions & 91 deletions

Dockerfiles/Dockerfile.oid4vc-issuance

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ RUN npm run build oid4vc-issuance
2727
FROM node:18-alpine
2828
# Install OpenSSL
2929
RUN apk add --no-cache openssl
30-
3130
# Set the working directory
3231
WORKDIR /app
3332

@@ -40,4 +39,4 @@ COPY --from=build /app/libs/ ./libs/
4039
COPY --from=build /app/node_modules ./node_modules
4140

4241
# Set the command to run the microservice
43-
CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/oid4vc-issuance/main.js"]
42+
CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/oid4vc-issuance/main.js"]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { IsDefined, IsEnum, IsOptional, IsString, ValidateNested } from 'class-validator';
3+
import { Type } from 'class-transformer';
4+
import { SignerOption } from '@prisma/client';
5+
import { ResponseMode } from '@credebl/enum/enum';
6+
7+
class RequestSignerDto {
8+
@ApiProperty({ enum: SignerOption, example: SignerOption.DID })
9+
@IsDefined()
10+
@IsEnum(SignerOption)
11+
method: SignerOption;
12+
}
13+
14+
export class CreateIntentBasedVerificationDto {
15+
@ApiProperty({ description: 'Intent name to lookup template for', example: 'kyc-intent' })
16+
@IsDefined()
17+
@IsString()
18+
intent: string;
19+
20+
@ApiProperty({ enum: ResponseMode, example: ResponseMode.DIRECT_POST_JWT })
21+
@IsDefined()
22+
@IsEnum(ResponseMode)
23+
responseMode: ResponseMode;
24+
25+
@ApiPropertyOptional({ type: RequestSignerDto, description: 'Optional request signer option' })
26+
@IsOptional()
27+
@ValidateNested()
28+
@Type(() => RequestSignerDto)
29+
requestSigner?: RequestSignerDto;
30+
}

apps/api-gateway/src/oid4vc-verification/dtos/oid4vc-verifier-presentation.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {
1515
Matches
1616
} from 'class-validator';
1717
import { Type } from 'class-transformer';
18-
import { SignerOption } from '@prisma/client';
1918
import { ResponseMode } from '@credebl/enum/enum';
19+
import { SignerOption } from '@prisma/client';
2020

2121
/**
2222
* DTO for verification-presentation query parameters.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { IsEnum, IsNotEmpty, IsObject, IsString } from 'class-validator';
2+
import { ApiProperty, PartialType } from '@nestjs/swagger';
3+
import { SignerOption } from '@prisma/client';
4+
5+
export class CreateVerificationTemplateDto {
6+
@ApiProperty({
7+
description: 'Name of the verification template',
8+
example: 'KYC Verification Template'
9+
})
10+
@IsString()
11+
@IsNotEmpty()
12+
name: string;
13+
14+
@ApiProperty({
15+
description: 'JSON configuration for the verification template',
16+
example: {
17+
dcql: {
18+
query: {
19+
combine: 'all',
20+
credentials: [
21+
{
22+
id: 'example-dc_sd_jwt',
23+
format: 'dc+sd-jwt',
24+
meta: {
25+
vct: 'urn:example:vc+sd-jwt'
26+
},
27+
require_cryptographic_holder_binding: true,
28+
claims: [
29+
{
30+
path: ['full_name'],
31+
intent_to_retain: true
32+
}
33+
]
34+
}
35+
]
36+
}
37+
}
38+
}
39+
})
40+
@IsObject()
41+
@IsNotEmpty()
42+
templateJson: object;
43+
44+
@ApiProperty({ enum: SignerOption, description: 'Signer option type' })
45+
@IsEnum(SignerOption)
46+
signerOption!: SignerOption;
47+
}
48+
49+
export class UpdateVerificationTemplateDto extends PartialType(CreateVerificationTemplateDto) {}

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

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ import { Oid4vcVerificationService } from './oid4vc-verification.service';
4848
import { CreateVerifierDto, UpdateVerifierDto } from './dtos/oid4vc-verifier.dto';
4949
import { PresentationRequestDto, VerificationPresentationQueryDto } from './dtos/oid4vc-verifier-presentation.dto';
5050
import { Oid4vpPresentationWhDto } from '../oid4vc-issuance/dtos/oid4vp-presentation-wh.dto';
51+
import { CreateVerificationTemplateDto, UpdateVerificationTemplateDto } from './dtos/verification-template.dto';
52+
import { CreateIntentBasedVerificationDto } from './dtos/create-intent-based-verification.dto';
5153

5254
@Controller()
5355
@UseFilters(CustomExceptionFilter)
@@ -305,6 +307,63 @@ export class Oid4vcVerificationController {
305307
return res.status(HttpStatus.CREATED).json(finalResponse);
306308
}
307309

310+
@Post('/orgs/:orgId/oid4vp/intent-based-verification-presentation')
311+
@ApiOperation({
312+
summary: 'Create intent-based verification presentation',
313+
description: 'Creates a new verification presentation using an intent template for the specified organization.'
314+
})
315+
@ApiResponse({
316+
status: HttpStatus.CREATED,
317+
description: 'Verification presentation created successfully.',
318+
type: ApiResponseDto
319+
})
320+
@ApiBearerAuth()
321+
@Roles(OrgRoles.OWNER)
322+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
323+
async createIntentBasedVerificationPresentation(
324+
@Param(
325+
'orgId',
326+
new ParseUUIDPipe({
327+
exceptionFactory: (): Error => {
328+
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
329+
}
330+
})
331+
)
332+
orgId: string,
333+
@Query(
334+
'verifierId',
335+
new ParseUUIDPipe({
336+
exceptionFactory: (): Error => {
337+
throw new BadRequestException('Invalid verifier ID');
338+
}
339+
})
340+
)
341+
verifierId: string,
342+
@User() user: user,
343+
@Body() createIntentDto: CreateIntentBasedVerificationDto,
344+
@Res() res: Response
345+
): Promise<Response> {
346+
this.logger.debug(
347+
`[createIntentBasedVerificationPresentation] Called with orgId=${orgId}, verifierId=${verifierId}, intent=${createIntentDto?.intent}, user=${user.id}`
348+
);
349+
350+
const presentation = await this.oid4vcVerificationService.createIntentBasedVerificationPresentation(
351+
orgId,
352+
verifierId,
353+
createIntentDto,
354+
user
355+
);
356+
357+
this.logger.debug(`[createIntentBasedVerificationPresentation] Presentation created successfully`);
358+
359+
const finalResponse: IResponse = {
360+
statusCode: HttpStatus.CREATED,
361+
message: ResponseMessages.oid4vpSession.success.create,
362+
data: presentation
363+
};
364+
return res.status(HttpStatus.CREATED).json(finalResponse);
365+
}
366+
308367
@Get('/orgs/:orgId/oid4vp/verifier-presentation')
309368
@ApiOperation({
310369
summary: 'Get OID4VP verifier presentation details',
@@ -448,6 +507,7 @@ export class Oid4vcVerificationController {
448507
});
449508

450509
if (webhookUrl) {
510+
this.logger.log(`posting webhook response to webhook url`);
451511
await this.oid4vcVerificationService
452512
._postWebhookResponse(webhookUrl, { data: oid4vpPresentationWhDto })
453513
.catch((error) => {
@@ -457,4 +517,205 @@ export class Oid4vcVerificationController {
457517

458518
return res.status(HttpStatus.CREATED).json(finalResponse);
459519
}
520+
521+
@Post('/orgs/:orgId/oid4vp/verification-template')
522+
@ApiOperation({
523+
summary: 'Create verification template',
524+
description: 'Creates a new verification template for the specified organization.'
525+
})
526+
@ApiResponse({
527+
status: HttpStatus.CREATED,
528+
description: 'Verification template created successfully.',
529+
type: ApiResponseDto
530+
})
531+
@ApiBearerAuth()
532+
@Roles(OrgRoles.OWNER)
533+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
534+
async createVerificationTemplate(
535+
@Param(
536+
'orgId',
537+
new ParseUUIDPipe({
538+
exceptionFactory: (): Error => {
539+
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
540+
}
541+
})
542+
)
543+
orgId: string,
544+
@User() user: user,
545+
@Body() createTemplateDto: CreateVerificationTemplateDto,
546+
@Res() res: Response
547+
): Promise<Response> {
548+
this.logger.debug(`[createVerificationTemplate] Called with orgId=${orgId}, user=${user.id}`);
549+
550+
const template = await this.oid4vcVerificationService.createVerificationTemplate(createTemplateDto, orgId, user);
551+
552+
this.logger.debug(`[createVerificationTemplate] Template created: ${template['id']}`);
553+
554+
const finalResponse: IResponse = {
555+
statusCode: HttpStatus.CREATED,
556+
message: 'Verification template created successfully',
557+
data: template
558+
};
559+
return res.status(HttpStatus.CREATED).json(finalResponse);
560+
}
561+
562+
@Get('/orgs/:orgId/oid4vp/verification-template')
563+
@ApiOperation({
564+
summary: 'Get verification template(s)',
565+
description: 'Retrieves verification template(s) for the specified organization.'
566+
})
567+
@ApiQuery({
568+
name: 'templateId',
569+
required: false,
570+
type: String,
571+
description: 'UUID of the template (optional)'
572+
})
573+
@ApiResponse({
574+
status: HttpStatus.OK,
575+
description: 'Verification template(s) retrieved successfully.',
576+
type: ApiResponseDto
577+
})
578+
@ApiBearerAuth()
579+
@Roles(OrgRoles.OWNER)
580+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
581+
async getVerificationTemplates(
582+
@Param(
583+
'orgId',
584+
new ParseUUIDPipe({
585+
exceptionFactory: (): Error => {
586+
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
587+
}
588+
})
589+
)
590+
orgId: string,
591+
@Res() res: Response,
592+
@Query(
593+
'templateId',
594+
new ParseUUIDPipe({
595+
version: '4',
596+
optional: true,
597+
exceptionFactory: (): Error => {
598+
throw new BadRequestException('Invalid template ID');
599+
}
600+
})
601+
)
602+
templateId?: string
603+
): Promise<Response> {
604+
this.logger.debug(`[getVerificationTemplates] Called with orgId=${orgId}, templateId=${templateId ?? 'all'}`);
605+
606+
const templates = await this.oid4vcVerificationService.getVerificationTemplates(orgId, templateId);
607+
608+
this.logger.debug(`[getVerificationTemplates] Templates fetched successfully`);
609+
const finalResponse: IResponse = {
610+
statusCode: HttpStatus.OK,
611+
message: 'Verification template(s) retrieved successfully',
612+
data: templates
613+
};
614+
return res.status(HttpStatus.OK).json(finalResponse);
615+
}
616+
617+
@Put('/orgs/:orgId/oid4vp/verification-template/:templateId')
618+
@ApiOperation({
619+
summary: 'Update verification template',
620+
description: 'Updates an existing verification template for the specified organization.'
621+
})
622+
@ApiResponse({
623+
status: HttpStatus.OK,
624+
description: 'Verification template updated successfully.',
625+
type: ApiResponseDto
626+
})
627+
@ApiBearerAuth()
628+
@Roles(OrgRoles.OWNER)
629+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
630+
async updateVerificationTemplate(
631+
@Param(
632+
'orgId',
633+
new ParseUUIDPipe({
634+
exceptionFactory: (): Error => {
635+
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
636+
}
637+
})
638+
)
639+
orgId: string,
640+
@Param(
641+
'templateId',
642+
new ParseUUIDPipe({
643+
exceptionFactory: (): Error => {
644+
throw new BadRequestException('Invalid template ID');
645+
}
646+
})
647+
)
648+
templateId: string,
649+
@User() user: user,
650+
@Body() updateTemplateDto: UpdateVerificationTemplateDto,
651+
@Res() res: Response
652+
): Promise<Response> {
653+
this.logger.debug(
654+
`[updateVerificationTemplate] Called with orgId=${orgId}, templateId=${templateId}, user=${user.id}`
655+
);
656+
657+
const template = await this.oid4vcVerificationService.updateVerificationTemplate(
658+
templateId,
659+
updateTemplateDto,
660+
orgId,
661+
user
662+
);
663+
664+
this.logger.debug(`[updateVerificationTemplate] Template updated: ${template['id']}`);
665+
666+
const finalResponse: IResponse = {
667+
statusCode: HttpStatus.OK,
668+
message: 'Verification template updated successfully',
669+
data: template
670+
};
671+
return res.status(HttpStatus.OK).json(finalResponse);
672+
}
673+
674+
@Delete('/orgs/:orgId/oid4vp/verification-template/:templateId')
675+
@ApiOperation({
676+
summary: 'Delete verification template',
677+
description: 'Deletes a verification template for the specified organization.'
678+
})
679+
@ApiResponse({
680+
status: HttpStatus.OK,
681+
description: 'Verification template deleted successfully.',
682+
type: ApiResponseDto
683+
})
684+
@ApiBearerAuth()
685+
@Roles(OrgRoles.OWNER)
686+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
687+
async deleteVerificationTemplate(
688+
@Param(
689+
'orgId',
690+
new ParseUUIDPipe({
691+
exceptionFactory: (): Error => {
692+
throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId);
693+
}
694+
})
695+
)
696+
orgId: string,
697+
@Param(
698+
'templateId',
699+
new ParseUUIDPipe({
700+
exceptionFactory: (): Error => {
701+
throw new BadRequestException('Invalid template ID');
702+
}
703+
})
704+
)
705+
templateId: string,
706+
@Res() res: Response
707+
): Promise<Response> {
708+
this.logger.debug(`[deleteVerificationTemplate] Called with orgId=${orgId}, templateId=${templateId}`);
709+
710+
const template = await this.oid4vcVerificationService.deleteVerificationTemplate(orgId, templateId);
711+
712+
this.logger.debug(`[deleteVerificationTemplate] Template deleted: ${templateId}`);
713+
714+
const finalResponse: IResponse = {
715+
statusCode: HttpStatus.OK,
716+
message: 'Verification template deleted successfully',
717+
data: template
718+
};
719+
return res.status(HttpStatus.OK).json(finalResponse);
720+
}
460721
}

0 commit comments

Comments
 (0)