Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ export class MdocNamespaceDto {
}
export class MdocTemplateDto {
@ApiProperty({
description: 'Document type (required when format is "mso_mdoc"; must NOT be provided when format is "vc+sd-jwt")',
example: 'org.iso.23220.photoID.1'
description: 'Document type (required when format is "mso_mdoc"; must NOT be provided when format is "dc+sd-jwt")',
example: 'org.iso.18013.5.1.mDL'
})
//@ValidateIf((o: CreateCredentialTemplateDto) => 'mso_mdoc' === o.format)
@IsString()
Expand All @@ -189,7 +189,7 @@ export class MdocTemplateDto {
export class SdJwtTemplateDto {
@ApiProperty({
description:
'Verifiable Credential Type (required when format is "vc+sd-jwt"; must NOT be provided when format is "mso_mdoc")',
'Verifiable Credential Type (required when format is "dc+sd-jwt"; must NOT be provided when format is "mso_mdoc")',
example: 'BirthCertificateCredential-sdjwt'
})
// @ValidateIf((o: CreateCredentialTemplateDto) => 'vc+sd-jwt' === o.format)
Expand Down Expand Up @@ -230,7 +230,7 @@ export class CreateCredentialTemplateDto {
format: CredentialFormat;

@ValidateIf((o: CreateCredentialTemplateDto) => CredentialFormat.SdJwtVc === o.format)
@IsEmpty({ message: 'doctype must not be provided when format is "vc+sd-jwt"' })
@IsEmpty({ message: 'doctype must not be provided when format is "dc+sd-jwt"' })
readonly _doctypeAbsentGuard?: unknown;

@ValidateIf((o: CreateCredentialTemplateDto) => CredentialFormat.Mdoc === o.format)
Expand Down
5 changes: 4 additions & 1 deletion apps/oid4vc-issuance/constant/issuance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ import { AccessTokenSignerKeyType } from '../interfaces/oid4vc-issuance.interfac

export const dpopSigningAlgValuesSupported = ['RS256', 'ES256', 'EdDSA'];
export const credentialConfigurationsSupported = {};
export const accessTokenSignerKeyType = 'ed25519' as AccessTokenSignerKeyType;
export const accessTokenSignerKeyType = { kty: 'OKP', crv: 'Ed25519' } as {
kty: string;
crv: AccessTokenSignerKeyType;
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
export const batchCredentialIssuanceDefault = 0;
8 changes: 4 additions & 4 deletions apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface CredentialConfiguration {
doctype?: string;
scope: string;
claims: Claim[];
credential_signing_alg_values_supported: string[];
credential_signing_alg_values_supported: string[] | number[];
cryptographic_binding_methods_supported: string[];
display: Display[];
}
Expand All @@ -54,7 +54,7 @@ export interface AuthorizationServerConfig {
export interface IssuerCreation {
authorizationServerUrl: string;
issuerId: string;
accessTokenSignerKeyType?: AccessTokenSignerKeyType;
accessTokenSignerKeyType?: { kty: string; crv: string };
display: Display[];
dpopSigningAlgValuesSupported?: string[];
authorizationServerConfigs: AuthorizationServerConfig;
Expand All @@ -67,7 +67,7 @@ export interface IssuerInitialConfig {
display: Display[] | {};
// eslint-disable-next-line @typescript-eslint/ban-types
authorizationServerConfigs: AuthorizationServerConfig | {};
accessTokenSignerKeyType: AccessTokenSignerKeyType;
accessTokenSignerKeyType: { kty: string; crv: string };
dpopSigningAlgValuesSupported: string[];
batchCredentialIssuance?: object;
credentialConfigurationsSupported: object;
Expand All @@ -87,7 +87,7 @@ export interface initialIssuerDetails {
}

export enum AccessTokenSignerKeyType {
ED25519 = 'ed25519'
ED25519 = 'Ed25519'
}

export interface IssuerUpdation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ISignerOption {
method: SignerMethodOption;
did?: string;
x5c?: string[];
keyId?: string;
}

export enum AuthenticationType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const DEFAULT_TXCODE = {
/** Map DB format string -> API enum */
function mapDbFormatToApiFormat(dbFormat: string): CredentialFormat {
const normalized = (dbFormat ?? '').toLowerCase();
if (['sd-jwt', 'vc+sd-jwt', 'sdjwt', 'sd+jwt-vc'].includes(normalized)) {
if (['sd-jwt', 'dc+sd-jwt', 'sdjwt', 'sd+jwt-vc'].includes(normalized)) {
return CredentialFormat.SdJwtVc;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if ('mso_mdoc' === normalized || 'mso-mdoc' === normalized || 'mdoc' === normalized) {
Expand Down
172 changes: 119 additions & 53 deletions apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { oidc_issuer, Prisma } from '@prisma/client';
import { batchCredentialIssuanceDefault } from '../../constant/issuance';
import { CreateOidcCredentialOffer } from '../../interfaces/oid4vc-issuer-sessions.interfaces';
import { IssuerResponse } from 'apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces';
import {
Claim,
CredentialAttribute,
MdocTemplate,
SdJwtTemplate
} from 'apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces';
import { Claim, MdocTemplate, SdJwtTemplate } from 'apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces';
import { CredentialFormat } from '@credebl/enum/enum';

type AttributeDisplay = { name: string; locale: string };
Expand Down Expand Up @@ -44,19 +39,25 @@ type CredentialConfig = {
vct?: string;
scope: string;
doctype?: string;
claims: Claim[];
credential_signing_alg_values_supported: string[];

credential_signing_alg_values_supported: string[] | number[];
cryptographic_binding_methods_supported: string[];
display: { name: string; description?: string; locale?: string }[];

credential_metadata: {
claims: Claim[];
display: CredentialDisplayItem[];
};
};

type CredentialConfigurationsSupported = {
credentialConfigurationsSupported: Record<string, CredentialConfig>;
};

// ---- Static Lists (as requested) ----
const STATIC_CREDENTIAL_ALGS = ['ES256', 'EdDSA'] as const;
const STATIC_BINDING_METHODS = ['did:key'] as const;
const STATIC_CREDENTIAL_ALGS_FOR_SDJWT = ['ES256', 'EdDSA'] as const;
const STATIC_CREDENTIAL_ALGS_FOR_MDOC = [-7, -9] as const;
const STATIC_BINDING_METHODS_FOR_SDJWT = ['jwk'] as const;
const STATIC_BINDING_METHODS_FOR_MDOC = ['cose_key'] as const; // We need to test 'did:key', 'did:web', 'did:jwk', 'jwk',

// Safe coercion helpers
function coerceJsonObject<T>(v: Prisma.JsonValue): T | null {
Expand Down Expand Up @@ -243,61 +244,118 @@ export function encodeIssuerPublicId(publicIssuerId: string): string {
/**
* Recursively builds a nested claims object from a list of attributes.
*/
function buildNestedClaims(attributes: CredentialAttribute[]): Record<string, Claim> {
const claims: Record<string, Claim> = {};
// function buildNestedClaims(attributes: CredentialAttribute[]): Record<string, Claim> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we remove this commented code?

// const claims: Record<string, Claim> = {};

// for (const attr of attributes) {
// const node: Claim = {};

// // ✅ include display info
// if (attr.display?.length) {
// node.display = attr.display.map((d) => ({
// name: d.name,
// locale: d.locale
// }));
// }

// // ✅ include mandatory flag
// if (attr.mandatory) {
// node.mandatory = true;
// }

// // ✅ handle nested children recursively
// if (attr.children?.length) {
// const childClaims = buildNestedClaims(attr.children);
// Object.assign(node, childClaims); // merge children into current node
// }

// claims[attr.key] = node;
// }

// return claims;
// }
// function generateObjects(
// namespace: string,
// attributes: any[],
// parentPath: string[] = []
// ): any[] {
// const result: any[] = [];

// for (const attr of attributes) {
// const currentPath = [...parentPath, attr.key];

// // If display exists, create object
// if (attr.display) {
// result.push({
// path: [namespace, ...currentPath],
// display: attr.display
// });
// }

// // Recurse into children
// if (attr.children?.length) {
// result.push(
// ...generateObjects(namespace, attr.children, currentPath)
// );
// }
// }
// console.log('generateObjects - result', JSON.stringify(result), '\n\n\n');
// return result;
// }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function generateClaims(attributes: any[], namespace?: string, parentPath: string[] = []): Claim[] {
const result: Claim[] = [];

for (const attr of attributes) {
const node: Claim = {};

// ✅ include display info
if (attr.display?.length) {
node.display = attr.display.map((d) => ({
name: d.name,
locale: d.locale
}));
const currentPath = [...parentPath, attr.key];

const path = namespace ? [namespace, ...currentPath] : currentPath;

const claim: Claim = { path };

if (attr.display) {
claim.display = attr.display;
}

// ✅ include mandatory flag
if (attr.mandatory) {
node.mandatory = true;
if (true === attr.mandatory) {
claim.mandatory = true;
}

// ✅ handle nested children recursively
// Always push the claim (even if it only has path)
result.push(claim);

// Handle nested children
if (attr.children?.length) {
const childClaims = buildNestedClaims(attr.children);
Object.assign(node, childClaims); // merge children into current node
result.push(...generateClaims(attr.children, namespace, currentPath));
}

claims[attr.key] = node;
}

return claims;
return result;
}

/**
* Builds claims object for both SD-JWT and MDOC credential templates.
*/
//TODO: Remove any type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function buildClaimsFromTemplate(template: SdJwtTemplate | MdocTemplate): Record<string, any> {
function buildClaimsFromTemplate(template: SdJwtTemplate | MdocTemplate): Record<string, Claim> | Claim[] {
// ✅ MDOC case — handle namespaces
if ((template as MdocTemplate).namespaces) {
const mdocTemplate = template as MdocTemplate;

//TODO: Remove any type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const claims: Record<string, any> = {};

const claims: Claim[] = [];
for (const ns of mdocTemplate.namespaces) {
claims[ns.namespace] = buildNestedClaims(ns.attributes);
}
const generated = generateClaims(ns.attributes, ns.namespace);

claims.push(...generated);
}
return claims;
}

// ✅ SD-JWT case — flat attributes
const sdjwtTemplate = template as SdJwtTemplate;
return buildNestedClaims(sdjwtTemplate.attributes);
const claims = generateClaims(sdjwtTemplate.attributes);
return claims;
}

//TODO: Fix this eslint issue
Expand All @@ -316,14 +374,17 @@ export function buildSdJwtCredentialConfig(name: string, template: SdJwtTemplate
format: CredentialFormat.SdJwtVc,
scope: credentialScope,
vct: template.vct,
credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS],
cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS],
// proof_types_supported: {
// jwt: {
// proof_signing_alg_values_supported: ['ES256']
// }
// },
claims
credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_SDJWT],
cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS_FOR_SDJWT],
proof_types_supported: {
jwt: {
proof_signing_alg_values_supported: ['ES256', 'EdDSA']
}
},
credential_metadata: {
claims,
display: []
}
}
};
}
Expand All @@ -339,7 +400,6 @@ export function buildMdocCredentialConfig(name: string, template: MdocTemplate)
const credentialScope = `openid4vc:${template.doctype}-${formatSuffix}`;

const claims = buildClaimsFromTemplate(template);

// for (const ns of template.namespaces) {
// claims.push(...buildClaimsFromAttributes(ns.attributes, [ns.namespace]));
// }
Expand All @@ -349,9 +409,17 @@ export function buildMdocCredentialConfig(name: string, template: MdocTemplate)
format: CredentialFormat.Mdoc,
scope: credentialScope,
doctype: template.doctype,
credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS],
cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS],
claims
credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_MDOC],
cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS_FOR_MDOC],
proof_types_supported: {
jwt: {
proof_signing_alg_values_supported: ['ES256', 'EdDSA']
}
},
credential_metadata: {
claims,
display: []
}
}
};
}
Expand Down Expand Up @@ -387,7 +455,6 @@ export function buildCredentialConfigurationsSupported(templateRows: any): Recor
templateToBuild,
format === CredentialFormat.Mdoc ? CredentialFormat.Mdoc : CredentialFormat.SdJwtVc
);

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

// Prepare the display configuration
Expand All @@ -406,12 +473,11 @@ export function buildCredentialConfigurationsSupported(templateRows: any): Recor

// eslint-disable-next-line prefer-destructuring
const dynamicKey = Object.keys(credentialConfig)[0];
Object.assign(credentialConfig[dynamicKey], {
Object.assign(credentialConfig[dynamicKey].credential_metadata, {
display: displayConfigurations
});

Object.assign(credentialConfigMap, credentialConfig);
}

return credentialConfigMap; // ✅ Return flat map, not nested object
}
7 changes: 4 additions & 3 deletions apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ export class Oid4vcIssuanceService {
}
signerOptions.push({
method: SignerMethodOption.X5C,
x5c: [activeCertificate.certificateBase64]
x5c: [activeCertificate.certificateBase64],
keyId: activeCertificate.keyId
});
activeCertificateDetails.push(activeCertificate);
}
Expand All @@ -564,7 +565,8 @@ export class Oid4vcIssuanceService {
}
signerOptions.push({
method: SignerMethodOption.X5C,
x5c: [activeCertificate.certificateBase64]
x5c: [activeCertificate.certificateBase64],
keyId: activeCertificate.keyId
});
activeCertificateDetails.push(activeCertificate);
}
Expand Down Expand Up @@ -754,7 +756,6 @@ export class Oid4vcIssuanceService {
const templates = await this.oid4vcIssuanceRepository.getTemplatesByIssuerId(issuerId);

const credentialConfigurationsSupported = buildCredentialConfigurationsSupported(templates);

return buildIssuerPayload({ credentialConfigurationsSupported }, issuerDetails);
} catch (error) {
this.logger.error(`[buildOidcIssuerPayload] - error: ${JSON.stringify(error)}`);
Expand Down
1 change: 1 addition & 0 deletions apps/x509/src/interfaces/x509.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface CreateX509CertificateEntity {
certificateBase64: string;
createdBy: string;
lastChangedBy: string;
keyId?: string;
}

export interface UpdateCertificateStatusDto {
Expand Down
Loading