Skip to content

Commit e5c749f

Browse files
authored
fix: trim schema metadata resolve payload (#786)
* fix: trim schema metadata resolve payload Signed-off-by: Mirko Mollik <mirko.mollik@eudi.sprind.org> * fix: trim schema metadata resolve payload ## Summary - remove `rawSchema` from the schema metadata resolve payload and related client typings - keep the resolve dialog focused on the generated DCQL output ## Validation - `pnpm build` in `apps/backend` - `pnpm -s exec tsc -p tsconfig.app.json --noEmit` in `apps/client` Signed-off-by: Mirko Mollik <mirko.mollik@eudi.sprind.org> * chore: set correct package Signed-off-by: Mirko Mollik <mirko.mollik@eudi.sprind.org> --------- Signed-off-by: Mirko Mollik <mirko.mollik@eudi.sprind.org>
1 parent d4d2bc0 commit e5c749f

36 files changed

Lines changed: 4998 additions & 3122 deletions

apps/backend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"@opentelemetry/sdk-metrics": "^2.6.1",
7474
"@opentelemetry/sdk-node": "^0.218.0",
7575
"@opentelemetry/semantic-conventions": "^1.40.0",
76-
"@owf/eudi-attestation-schema": "^0.2.0",
76+
"@owf/eudi-attestation-schema": "0.3.1-alpha-20260613182022",
7777
"@owf/eudi-lote": "^0.2.0",
7878
"@owf/mdoc": "^0.6.0",
7979
"@owf/token-status-list": "0.3.0-alpha-20260508181749",
@@ -143,4 +143,4 @@
143143
"vitest": "4.1.8"
144144
},
145145
"packageManager": "pnpm@10.13.1"
146-
}
146+
}

apps/backend/src/issuer/configuration/credentials/dto/schema-meta-config.dto.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ export class SchemaUriEntry {
7676
type: "object",
7777
additionalProperties: true,
7878
})
79+
@IsOptional()
7980
@IsObject()
80-
meta!: SchemaURIMeta;
81+
meta?: SchemaURIMeta;
8182
}
8283

8384
/**
@@ -121,12 +122,20 @@ export class TrustAuthorityEntry {
121122
description:
122123
"Optional verification material for external trusted authorities (for example a JWK). " +
123124
"For internal trust-list URLs, EUDIPLO resolves verification material from the database.",
124-
type: "object",
125-
additionalProperties: true,
125+
oneOf: [
126+
{
127+
type: "object",
128+
additionalProperties: true,
129+
},
130+
{
131+
type: "string",
132+
description:
133+
"JSON string representing an object. Parsed server-side for form submissions.",
134+
},
135+
],
126136
})
127137
@IsOptional()
128-
@IsObject()
129-
verificationMethod?: Record<string, unknown>;
138+
verificationMethod?: Record<string, unknown> | string;
130139
}
131140

132141
/**

apps/backend/src/issuer/configuration/credentials/schema-meta/schema-meta-adapter.service.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -293,24 +293,29 @@ export class SchemaMetaAdapterService {
293293
"rulebookURI",
294294
);
295295
const rawSchemaUris = config.schemaURIs ?? [];
296-
for (const entry of rawSchemaUris) {
297-
if (!entry.format || !entry.uri || !entry.meta) {
298-
throw new BadRequestException(
299-
"Each schemaURIs entry must include format, uri, and meta after preprocessing.",
300-
);
301-
}
302-
}
303296

304297
const schemaURIsWithIntegrity = await Promise.all(
305-
rawSchemaUris.map(async (entry) => ({
306-
format: entry.format as string,
307-
uri: entry.uri as string,
308-
metadata: entry.meta,
309-
integrity: await this.computeSri(
310-
entry.uri as string,
311-
`schemaURIs[${entry.format as string}]`,
312-
),
313-
})),
298+
rawSchemaUris.map(async (entry) => {
299+
const format = entry.format;
300+
const uri = entry.uri;
301+
const metadata = entry.meta;
302+
303+
if (!format || !uri || !metadata) {
304+
throw new BadRequestException(
305+
"Each schemaURIs entry must include format, uri, and meta after preprocessing.",
306+
);
307+
}
308+
309+
return {
310+
format,
311+
uri,
312+
metadata,
313+
integrity: await this.computeSri(
314+
uri,
315+
`schemaURIs[${format}]`,
316+
),
317+
};
318+
}),
314319
);
315320
const trustedAuthorities = await this.buildTrustedAuthorities(
316321
tenantId,

apps/backend/src/issuer/configuration/credentials/schema-meta/schema-metadata-signing.service.ts

Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {
99
import { buildJsonSchema } from "../utils";
1010
import { SchemaMetaAdapterService } from "./schema-meta-adapter.service";
1111

12+
type TrustedAuthorityInput = NonNullable<
13+
SignSchemaMetaConfigDto["config"]["trustedAuthorities"]
14+
>[number];
15+
1216
@Injectable()
1317
export class SchemaMetadataSigningService {
1418
constructor(
@@ -17,23 +21,33 @@ export class SchemaMetadataSigningService {
1721
private readonly registrarService: RegistrarService,
1822
) {}
1923

24+
private extractConfiguredVct(credentialConfig: {
25+
vct?: unknown;
26+
}): string | undefined {
27+
if (typeof credentialConfig.vct === "string") {
28+
return credentialConfig.vct;
29+
}
30+
31+
if (
32+
credentialConfig.vct &&
33+
typeof credentialConfig.vct === "object" &&
34+
"vct" in credentialConfig.vct &&
35+
typeof (credentialConfig.vct as { vct?: unknown }).vct === "string"
36+
) {
37+
return (credentialConfig.vct as { vct: string }).vct;
38+
}
39+
40+
return undefined;
41+
}
42+
2043
private deriveSchemaUriMetadata(
2144
credentialConfig: Awaited<
2245
ReturnType<CredentialConfigService["getById"]>
2346
>,
2447
format: string,
2548
): SchemaURIMeta {
2649
if (format === "dc+sd-jwt") {
27-
const configuredVct =
28-
typeof credentialConfig.vct === "string"
29-
? credentialConfig.vct
30-
: credentialConfig.vct &&
31-
typeof credentialConfig.vct === "object" &&
32-
"vct" in credentialConfig.vct &&
33-
typeof (credentialConfig.vct as { vct?: unknown })
34-
.vct === "string"
35-
? (credentialConfig.vct as { vct: string }).vct
36-
: undefined;
50+
const configuredVct = this.extractConfiguredVct(credentialConfig);
3751

3852
if (!configuredVct) {
3953
throw new BadRequestException(
@@ -64,6 +78,75 @@ export class SchemaMetadataSigningService {
6478
);
6579
}
6680

81+
private parseVerificationMethod(
82+
verificationMethod: string | Record<string, unknown> | undefined,
83+
index: number,
84+
): Record<string, unknown> | undefined {
85+
if (verificationMethod === undefined) {
86+
return undefined;
87+
}
88+
89+
if (typeof verificationMethod === "string") {
90+
const trimmed = verificationMethod.trim();
91+
if (trimmed.length === 0) {
92+
return undefined;
93+
}
94+
95+
try {
96+
const parsed = JSON.parse(trimmed);
97+
if (
98+
!parsed ||
99+
Array.isArray(parsed) ||
100+
typeof parsed !== "object"
101+
) {
102+
throw new Error("verificationMethod must be a JSON object");
103+
}
104+
105+
return parsed as Record<string, unknown>;
106+
} catch (error) {
107+
throw new BadRequestException(
108+
`trustedAuthorities[${index}].verificationMethod must be valid JSON object: ${
109+
error instanceof Error ? error.message : "invalid JSON"
110+
}`,
111+
);
112+
}
113+
}
114+
115+
if (Array.isArray(verificationMethod)) {
116+
throw new BadRequestException(
117+
`trustedAuthorities[${index}].verificationMethod must be an object`,
118+
);
119+
}
120+
121+
return verificationMethod;
122+
}
123+
124+
private normalizeTrustedAuthority(
125+
entry: TrustedAuthorityInput,
126+
index: number,
127+
) {
128+
if (entry.trustListId) {
129+
return {
130+
trustListId: entry.trustListId,
131+
...(entry.isLoTE === undefined ? {} : { isLoTE: entry.isLoTE }),
132+
};
133+
}
134+
135+
const verificationMethod = this.parseVerificationMethod(
136+
entry.verificationMethod,
137+
index,
138+
);
139+
140+
return {
141+
...(entry.frameworkType
142+
? { frameworkType: entry.frameworkType }
143+
: {}),
144+
...(entry.value ? { value: entry.value } : {}),
145+
...(entry.isLoTE === undefined ? {} : { isLoTE: entry.isLoTE }),
146+
...(verificationMethod ? { verificationMethod } : {}),
147+
};
148+
}
149+
67150
private async uploadSchemaAssetFromCredentialConfig(
68151
tenantId: string,
69152
credentialConfigId: string,
@@ -142,6 +225,12 @@ export class SchemaMetadataSigningService {
142225
);
143226
}
144227

228+
if (!entry.meta) {
229+
throw new BadRequestException(
230+
"schemaURIs metadata is required for manual schema URI entries.",
231+
);
232+
}
233+
145234
const uploadedSchema =
146235
await this.registrarService.uploadSchemaMetadataAssetFromUrl(
147236
tenantId,
@@ -163,6 +252,29 @@ export class SchemaMetadataSigningService {
163252
};
164253
}
165254

255+
private normalizeConfigFromFormInput(
256+
config: SignSchemaMetaConfigDto["config"],
257+
): SignSchemaMetaConfigDto["config"] {
258+
const normalizedSchemaUris = (config.schemaURIs ?? []).map((entry) => ({
259+
...(entry.credentialConfigId
260+
? { credentialConfigId: entry.credentialConfigId }
261+
: {}),
262+
...(entry.format ? { format: entry.format } : {}),
263+
...(entry.uri ? { uri: entry.uri } : {}),
264+
...(entry.meta ? { meta: entry.meta } : {}),
265+
}));
266+
267+
const normalizedTrustedAuthorities = (
268+
config.trustedAuthorities ?? []
269+
).map((entry, index) => this.normalizeTrustedAuthority(entry, index));
270+
271+
return {
272+
...config,
273+
schemaURIs: normalizedSchemaUris,
274+
trustedAuthorities: normalizedTrustedAuthorities,
275+
};
276+
}
277+
166278
private async ensureSchemaUrisFromCredentialConfig(
167279
tenantId: string,
168280
config: SignSchemaMetaConfigDto["config"],
@@ -205,10 +317,12 @@ export class SchemaMetadataSigningService {
205317
tenantId: string,
206318
body: SignSchemaMetaConfigDto,
207319
) {
320+
const normalizedConfig = this.normalizeConfigFromFormInput(body.config);
321+
208322
const { config: derivedConfig, alreadyHosted } =
209323
await this.ensureSchemaUrisFromCredentialConfig(
210324
tenantId,
211-
body.config,
325+
normalizedConfig,
212326
body.credentialConfigId,
213327
);
214328

@@ -241,7 +355,7 @@ export class SchemaMetadataSigningService {
241355
body.credentialConfigId,
242356
);
243357
const schemaMetaForLink = {
244-
...(existing.schemaMeta ?? {}),
358+
...existing.schemaMeta,
245359
...configToSign,
246360
id: reservedId,
247361
};
@@ -261,15 +375,17 @@ export class SchemaMetadataSigningService {
261375
tenantId: string,
262376
body: SignVersionSchemaMetaConfigDto,
263377
) {
264-
if (!body.config.id) {
378+
const normalizedConfig = this.normalizeConfigFromFormInput(body.config);
379+
380+
if (!normalizedConfig.id) {
265381
throw new BadRequestException(
266382
"config.id is required when publishing a new version of an existing schema metadata entry",
267383
);
268384
}
269385

270386
const configToSign = await this.uploadSchemaMetaAssetsToRegistrar(
271387
tenantId,
272-
body.config,
388+
normalizedConfig,
273389
);
274390

275391
const signed =
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

33
import {
4-
type ClientOptions,
5-
type Config,
6-
createClient,
7-
createConfig,
4+
type Client,
5+
type ClientOptions,
6+
type Config,
7+
createClient,
8+
createConfig,
89
} from "./client";
910
import type { ClientOptions as ClientOptions2 } from "./types.gen";
1011

@@ -16,10 +17,10 @@ import type { ClientOptions as ClientOptions2 } from "./types.gen";
1617
* `setConfig()`. This is useful for example if you're using Next.js
1718
* to ensure your client always has the correct values.
1819
*/
19-
type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
20-
override?: Config<ClientOptions & T>,
20+
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
21+
override?: Config<ClientOptions & T>,
2122
) => Config<Required<ClientOptions> & T>;
2223

23-
export const client = createClient(
24-
createConfig<ClientOptions2>({ baseUrl: "http://localhost:3001" }),
24+
export const client: Client = createClient(
25+
createConfig<ClientOptions2>({ baseUrl: "http://localhost:3001" }),
2526
);

0 commit comments

Comments
 (0)