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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Dockerfiles/Dockerfile.x509
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Stage 1: Build the application
FROM node:18-alpine as build
# Install OpenSSL
RUN apk add --no-cache openssl
RUN npm install -g pnpm
# Set the working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package.json ./
COPY pnpm-workspace.yaml ./
#COPY package-lock.json ./

ENV PUPPETEER_SKIP_DOWNLOAD=true

# Install dependencies while ignoring scripts (including Puppeteer's installation)
RUN pnpm i --ignore-scripts

# Copy the rest of the application code
COPY . .
# RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate
RUN cd libs/prisma-service && npx prisma generate
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

Remove redundant Prisma generate from runtime CMD.

Prisma artifacts are already generated during the build stage (line 22). Running npx prisma generate again at runtime (line 45) is unnecessary and adds startup overhead.

If Prisma generate is truly needed at runtime (e.g., due to schema changes), consider regenerating it in the build stage only and relying on that output in the final image.

Also applies to: 45-45

🤖 Prompt for AI Agents
In Dockerfiles/Dockerfile.x509 around lines 22 and 45, you currently run `npx
prisma generate` during the build stage (line 22) and again at container runtime
(line 45); remove the redundant runtime `npx prisma generate` at line 45 so the
image uses the artifacts generated during build, and if runtime generation is
required instead, move or ensure the generation happens only in the build stage
and copy the generated Prisma client into the final image so no startup-time
generation is performed.


# Build the x509 service
RUN npm run build x509


# Stage 2: Create the final image
FROM node:18-alpine
# Install OpenSSL
RUN apk add --no-cache openssl
# RUN npm install -g pnpm
# Set the working directory
WORKDIR /app

# Copy the compiled code from the build stage
COPY --from=build /app/dist/apps/x509/ ./dist/apps/x509/

# Copy the libs folder from the build stage
COPY --from=build /app/libs/ ./libs/
#COPY --from=build /app/package.json ./
COPY --from=build /app/node_modules ./node_modules

# Set the command to run the microservice
CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/x509/main.js"]
Comment on lines +44 to +45
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

Move Prisma migrations to deployment pipeline, not container startup.

Running prisma migrate deploy on every container start can cause startup delays, race conditions across multiple instances, and cascading failures if the database is unavailable. Database migrations should typically run once during deployment via an init container, job, or pre-deployment script—not every time a container starts.

Consider restructuring to:

  1. Run Prisma migrations as a separate deployment step (e.g., Kubernetes Job or CI/CD pipeline).
  2. Make the app startup conditional on migration completion (e.g., via an init container).
  3. At minimum, add idempotency guards and retry logic if migrations must stay in the container.

For now, simplify the CMD:

-CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/x509/main.js"]
+CMD ["node", "dist/apps/x509/main.js"]

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Dockerfiles/Dockerfile.x509 around lines 44-45, the CMD runs `npx prisma
migrate deploy` on every container start which causes delays and race
conditions; remove the migration step from the container startup and simplify
the CMD to only start the app (and optionally run prisma generate if needed at
runtime), and move migrations to your deployment pipeline or an init
container/Kubernetes Job; if you cannot move them immediately, add idempotency
guards and retry/backoff around migrations, but the immediate fix is to delete
`npx prisma migrate deploy` from the CMD and ensure migrations are executed once
during deployment.

43 changes: 23 additions & 20 deletions apps/api-gateway/src/agent-service/dto/create-schema.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsArray } from 'class-validator';

export class CreateTenantSchemaDto {
@ApiProperty()
@IsString({ message: 'tenantId must be a string' }) @IsNotEmpty({ message: 'please provide valid tenantId' })
tenantId: string;

@ApiProperty()
@IsString({ message: 'schema version must be a string' }) @IsNotEmpty({ message: 'please provide valid schema version' })
schemaVersion: string;
@ApiProperty()
@IsString({ message: 'tenantId must be a string' })
@IsNotEmpty({ message: 'please provide valid tenantId' })
tenantId: string;

@ApiProperty()
@IsString({ message: 'schema name must be a string' }) @IsNotEmpty({ message: 'please provide valid schema name' })
schemaName: string;
@ApiProperty()
@IsString({ message: 'schema version must be a string' })
@IsNotEmpty({ message: 'please provide valid schema version' })
schemaVersion: string;

@ApiProperty()
@IsArray({ message: 'attributes must be an array' })
@IsString({ each: true })
@IsNotEmpty({ message: 'please provide valid attributes' })
attributes: string[];
@ApiProperty()
@IsString({ message: 'schema name must be a string' })
@IsNotEmpty({ message: 'please provide valid schema name' })
schemaName: string;

@ApiProperty()

@IsNotEmpty({ message: 'please provide orgId' })
orgId: string;
}
@ApiProperty()
@IsArray({ message: 'attributes must be an array' })
@IsString({ each: true })
// TODO: IsNotEmpty won't work for array. Must use @ArrayNotEmpty() instead
@IsNotEmpty({ message: 'please provide valid attributes' })
attributes: string[];
Comment on lines 20 to 27
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

Fix array validation: @isnotempty doesn’t enforce non-empty arrays; use @ArrayNotEmpty and element checks

Empty arrays will currently pass. Enforce at least one attribute and non-empty strings per element. Also set Swagger type for arrays.

Apply:

-  @ApiProperty()
+  @ApiProperty({ type: [String] })
   @IsArray({ message: 'attributes must be an array' })
-  @IsString({ each: true })
-  // TODO: IsNotEmpty won't work for array. Must use @ArrayNotEmpty() instead
-  @IsNotEmpty({ message: 'please provide valid attributes' })
+  @IsString({ each: true })
+  @ArrayNotEmpty({ message: 'please provide at least one attribute' })
+  @IsNotEmpty({ each: true, message: 'attribute must not be empty' })
   attributes: string[];

And update imports (outside this hunk):

-import { IsString, IsNotEmpty, IsArray } from 'class-validator';
+import { IsString, IsNotEmpty, IsArray, ArrayNotEmpty } from 'class-validator';

Optional (if duplicates are not allowed):

// @ArrayUnique({ message: 'attributes must be unique' })
🤖 Prompt for AI Agents
In apps/api-gateway/src/agent-service/dto/create-schema.dto.ts around lines
20–25, replace the incorrect @IsNotEmpty on the array with @ArrayNotEmpty to
enforce a non-empty array, add element-level non-empty checks by using
@IsNotEmpty({ each: true }) alongside the existing @IsString({ each: true }),
and set the Swagger type with @ApiProperty({ type: [String] }); also update
imports at the top to import ArrayNotEmpty (and optionally ArrayUnique if you
want uniqueness) from class-validator and ensure ApiProperty is configured for
array types.


@ApiProperty()
@IsNotEmpty({ message: 'please provide orgId' })
orgId: string;
Comment on lines +29 to +31
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

orgId missing type validation: add @IsString (or specific ID validator)

Without @IsString, numbers/objects could pass if non-empty, risking downstream failures.

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

Optional (if UUID):

-  @IsString({ message: 'orgId must be a string' })
+  @IsUUID('4', { message: 'orgId must be a valid UUID v4' })

Remember to import IsUUID if used.

📝 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
@ApiProperty()
@IsNotEmpty({ message: 'please provide orgId' })
orgId: string;
@ApiProperty()
@IsString({ message: 'orgId must be a string' })
@IsNotEmpty({ message: 'please provide orgId' })
orgId: string;
🤖 Prompt for AI Agents
In apps/api-gateway/src/agent-service/dto/create-schema.dto.ts around lines 27
to 29, the orgId property currently only has @IsNotEmpty and lacks a type
validator; add @IsString() to the decorators (or @IsUUID() if orgId should be a
UUID) and import the chosen validator from class-validator so non-string values
are rejected and downstream type assumptions are safe.

}
94 changes: 62 additions & 32 deletions apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { dateToSeconds } from '@credebl/common/date-only';

/* ========= disclosureFrame custom validator ========= */
function isDisclosureFrameValue(v: unknown): boolean {
Expand Down Expand Up @@ -117,6 +118,24 @@ function ExactlyOneOf(keys: string[], options?: ValidationOptions) {
return Validate(ExactlyOneOfConstraint, keys, options);
}

export class ValidityInfo {
@ApiProperty({
example: '2025-04-23T14:34:09.188Z',
required: true
})
@IsString()
@IsNotEmpty()
validFrom: Date;

@ApiProperty({
example: '2026-05-03T14:34:09.188Z',
required: true
})
@IsString()
@IsNotEmpty()
validUntil: Date;
}

/* ========= Request DTOs ========= */
export class CredentialRequestDto {
@ApiProperty({
Expand All @@ -137,13 +156,20 @@ export class CredentialRequestDto {
payload!: Record<string, unknown>;

@ApiPropertyOptional({
description: 'Selective disclosure: claim -> boolean (or nested map)',
example: { name: true, DOB: true, additionalProp3: false },
example: { validFrom: '2025-04-23T14:34:09.188Z', validUntil: '2026-05-03T14:34:09.188Z' },
required: false
})
@IsOptional()
@IsDisclosureFrame()
disclosureFrame?: Record<string, boolean | Record<string, boolean>>;
validityInfo?: ValidityInfo;
Comment on lines 139 to +166
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

Add nested validation for validityInfo.

The optional validityInfo field should use @ValidateNested() and @Type(() => ValidityInfo) to ensure its properties are validated when present.

Apply this diff:

   @ApiPropertyOptional({
     example: { validFrom: '2025-04-23T14:34:09.188Z', validUntil: '2026-05-03T14:34:09.188Z' },
     required: false
   })
   @IsOptional()
+  @ValidateNested()
+  @Type(() => ValidityInfo)
   validityInfo?: ValidityInfo;
📝 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
@ApiPropertyOptional({
description: 'Selective disclosure: claim -> boolean (or nested map)',
example: { name: true, DOB: true, additionalProp3: false },
example: { validFrom: '2025-04-23T14:34:09.188Z', validUntil: '2026-05-03T14:34:09.188Z' },
required: false
})
@IsOptional()
@IsDisclosureFrame()
disclosureFrame?: Record<string, boolean | Record<string, boolean>>;
validityInfo?: ValidityInfo;
@ApiPropertyOptional({
example: { validFrom: '2025-04-23T14:34:09.188Z', validUntil: '2026-05-03T14:34:09.188Z' },
required: false
})
@IsOptional()
@ValidateNested()
@Type(() => ValidityInfo)
validityInfo?: ValidityInfo;
🤖 Prompt for AI Agents
In apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts around lines
161 to 166, the optional validityInfo property lacks nested validation
annotations; add @ValidateNested() and @Type(() => ValidityInfo) above
validityInfo so that when provided its inner properties are validated, keeping
@IsOptional() and the existing @ApiPropertyOptional intact.


// @ApiPropertyOptional({
// description: 'Selective disclosure: claim -> boolean (or nested map)',
// example: { name: true, DOB: true, additionalProp3: false },
// required: false
// })
// @IsOptional()
// @IsDisclosureFrame()
// disclosureFrame?: Record<string, boolean | Record<string, boolean>>;
}

export class CreateOidcCredentialOfferDto {
Expand All @@ -157,25 +183,16 @@ export class CreateOidcCredentialOfferDto {
@Type(() => CredentialRequestDto)
credentials!: CredentialRequestDto[];

// XOR: exactly one present
@ApiPropertyOptional({ type: PreAuthorizedCodeFlowConfigDto })
@IsOptional()
@ValidateNested()
@Type(() => PreAuthorizedCodeFlowConfigDto)
preAuthorizedCodeFlowConfig?: PreAuthorizedCodeFlowConfigDto;

@IsOptional()
@ValidateNested()
@Type(() => AuthorizationCodeFlowConfigDto)
authorizationCodeFlowConfig?: AuthorizationCodeFlowConfigDto;
@ApiProperty({
example: 'preAuthorizedCodeFlow',
enum: ['preAuthorizedCodeFlow', 'authorizationCodeFlow'],
description: 'Authorization type'
})
@IsString()
@IsIn(['preAuthorizedCodeFlow', 'authorizationCodeFlow'])
authorizationType!: 'preAuthorizedCodeFlow' | 'authorizationCodeFlow';

issuerId?: string;

// host XOR rule
@ExactlyOneOf(['preAuthorizedCodeFlowConfig', 'authorizationCodeFlowConfig'], {
message: 'Provide exactly one of preAuthorizedCodeFlowConfig or authorizationCodeFlowConfig.'
})
private readonly _exactlyOne?: unknown;
}

export class GetAllCredentialOfferDto {
Expand Down Expand Up @@ -266,20 +283,33 @@ export class CredentialDto {

@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'
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'
}
},
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;
Expand Down
Loading