From 35c2fa27ee6551511263f62b974eb18d591026dd Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 11:33:45 +0100 Subject: [PATCH 1/6] refactor: make VerifiablePresentation to TokenContentVerifiablePresentation --- .../app-runtime/src/AppStringProcessor.ts | 4 +- .../modules/openid4vc/OpenId4VcController.ts | 4 +- .../src/modules/openid4vc/local/Holder.ts | 6 +- .../types/VerifiablePresentation.ts | 69 ------------------- .../content/src/attributes/types/index.ts | 1 - packages/content/src/index.ts | 1 + .../TokenContentVerifiablePresentation.ts | 45 ++++++++++++ packages/content/src/tokens/index.ts | 1 + .../test/consumption/openid4vc.test.ts | 8 +-- 9 files changed, 58 insertions(+), 81 deletions(-) delete mode 100644 packages/content/src/attributes/types/VerifiablePresentation.ts create mode 100644 packages/content/src/tokens/TokenContentVerifiablePresentation.ts create mode 100644 packages/content/src/tokens/index.ts diff --git a/packages/app-runtime/src/AppStringProcessor.ts b/packages/app-runtime/src/AppStringProcessor.ts index 167d13381..1f494ae75 100644 --- a/packages/app-runtime/src/AppStringProcessor.ts +++ b/packages/app-runtime/src/AppStringProcessor.ts @@ -2,7 +2,7 @@ import { OpenId4VciResolvedCredentialOffer } from "@credo-ts/openid4vc"; import { ILogger, ILoggerFactory } from "@js-soft/logging-abstractions"; import { Serializable } from "@js-soft/ts-serval"; import { EventBus, Result } from "@js-soft/ts-utils"; -import { VerifiablePresentation } from "@nmshd/content"; +import { TokenContentVerifiablePresentation } from "@nmshd/content"; import { ICoreAddress, Reference } from "@nmshd/core-types"; import { AnonymousServices, DeviceMapper, RuntimeServices } from "@nmshd/runtime"; import { BackboneIds, TokenContentDeviceSharedSecret } from "@nmshd/transport"; @@ -292,7 +292,7 @@ export class AppStringProcessor { case "Token": const tokenContent = this.parseTokenContent(result.value.value.content); - if (tokenContent instanceof VerifiablePresentation) { + if (tokenContent instanceof TokenContentVerifiablePresentation) { // TODO: add technical validation await uiBridge.showVerifiablePresentation(account, result.value.value, true); break; diff --git a/packages/consumption/src/modules/openid4vc/OpenId4VcController.ts b/packages/consumption/src/modules/openid4vc/OpenId4VcController.ts index c20ac2a80..e1de7aa7c 100644 --- a/packages/consumption/src/modules/openid4vc/OpenId4VcController.ts +++ b/packages/consumption/src/modules/openid4vc/OpenId4VcController.ts @@ -1,6 +1,6 @@ import { DcqlValidCredential, W3cJsonCredential } from "@credo-ts/core"; import { OpenId4VciResolvedCredentialOffer, OpenId4VpResolvedAuthorizationRequest } from "@credo-ts/openid4vc"; -import { VerifiableCredential, VerifiablePresentation } from "@nmshd/content"; +import { TokenContentVerifiablePresentation, VerifiableCredential } from "@nmshd/content"; import { ConsumptionBaseController } from "../../consumption/ConsumptionBaseController"; import { ConsumptionController } from "../../consumption/ConsumptionController"; import { ConsumptionControllerName } from "../../consumption/ConsumptionControllerName"; @@ -130,7 +130,7 @@ export class OpenId4VcController extends ConsumptionBaseController { return { status: serverResponse.status, message: serverResponse.body }; } - public async createPresentationTokenContent(credential: VerifiableCredential): Promise { + public async createPresentationTokenContent(credential: VerifiableCredential): Promise { return await this.holder.createPresentationTokenContent(credential); } } diff --git a/packages/consumption/src/modules/openid4vc/local/Holder.ts b/packages/consumption/src/modules/openid4vc/local/Holder.ts index 06e7f9ef7..aa0b6ecb3 100644 --- a/packages/consumption/src/modules/openid4vc/local/Holder.ts +++ b/packages/consumption/src/modules/openid4vc/local/Holder.ts @@ -13,7 +13,7 @@ import { X509Module } from "@credo-ts/core"; import { OpenId4VciCredentialResponse, OpenId4VcModule, type OpenId4VciResolvedCredentialOffer, type OpenId4VpResolvedAuthorizationRequest } from "@credo-ts/openid4vc"; -import { VerifiableCredential, VerifiablePresentation } from "@nmshd/content"; +import { TokenContentVerifiablePresentation, VerifiableCredential } from "@nmshd/content"; import { AccountController } from "@nmshd/transport"; import { AttributesController, OwnIdentityAttribute } from "../../attributes"; import { BaseAgent } from "./BaseAgent"; @@ -204,7 +204,7 @@ export class Holder extends BaseAgent> // hacky solution because credo doesn't support credentials without key binding // TODO: use credentials without key binding once supported - public async createPresentationTokenContent(credential: VerifiableCredential): Promise { + public async createPresentationTokenContent(credential: VerifiableCredential): Promise { if (credential.type !== ClaimFormat.SdJwtDc) throw new Error("Only SD-JWT credentials have been tested so far with token presentation"); const sdJwtVcApi = this.agent.dependencyManager.resolve(SdJwtVcApi); @@ -217,7 +217,7 @@ export class Holder extends BaseAgent> } }); - return VerifiablePresentation.from({ + return TokenContentVerifiablePresentation.from({ value: presentation, type: credential.type, displayInformation: credential.displayInformation diff --git a/packages/content/src/attributes/types/VerifiablePresentation.ts b/packages/content/src/attributes/types/VerifiablePresentation.ts deleted file mode 100644 index 9f2f2deff..000000000 --- a/packages/content/src/attributes/types/VerifiablePresentation.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { serialize, type, validate } from "@js-soft/ts-serval"; -import { AbstractAttributeValue, AbstractAttributeValueJSON, IAbstractAttributeValue } from "../AbstractAttributeValue"; -import { RenderHints, RenderHintsEditType, RenderHintsTechnicalType, ValueHints } from "../hints"; -import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "./proprietary/ProprietaryAttributeValue"; - -export interface VerifiablePresentationJSON extends AbstractAttributeValueJSON { - "@type": "VerifiablePresentation"; - value: string | Record; - type: string; - displayInformation?: Record[]; -} - -export interface IVerifiablePresentation extends IAbstractAttributeValue { - value: string | Record; - type: string; - displayInformation?: Record[]; -} - -@type("VerifiablePresentation") -export class VerifiablePresentation extends AbstractAttributeValue implements IVerifiablePresentation { - @serialize({ any: true }) - @validate({ customValidator: validateValue }) - public value: string | Record; - - @serialize() - @validate({ nullable: true }) - public type: string; - - @serialize() - @validate({ nullable: true, max: PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH }) - public displayInformation?: Record[]; - - public static get valueHints(): ValueHints { - return ValueHints.from({}); - } - - public static get renderHints(): RenderHints { - return RenderHints.from({ - editType: RenderHintsEditType.TextArea, - technicalType: RenderHintsTechnicalType.Unknown - }); - } - - public static from(value: IVerifiablePresentation | Omit): VerifiablePresentation { - return this.fromAny(value); - } - - public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): VerifiablePresentationJSON { - return super.toJSON(verbose, serializeAsString) as VerifiablePresentationJSON; - } -} - -function validateValue(value: any) { - try { - const string = JSON.stringify(value); - // the length correspondes to 50MB - maybe this needs to be restricted further in the future - if (string.length > 52428800) { - return "stringified value must not be longer than 52428800 characters"; - } - } catch (e) { - if (e instanceof SyntaxError) { - return "must be a valid JSON object"; - } - - return "could not validate value"; - } - - return undefined; -} diff --git a/packages/content/src/attributes/types/index.ts b/packages/content/src/attributes/types/index.ts index 17b345572..e7f261d7d 100644 --- a/packages/content/src/attributes/types/index.ts +++ b/packages/content/src/attributes/types/index.ts @@ -17,4 +17,3 @@ export * from "./relationship"; export * from "./statement"; export * from "./strings"; export * from "./VerifiableCredential"; -export * from "./VerifiablePresentation"; diff --git a/packages/content/src/index.ts b/packages/content/src/index.ts index d77129ba9..2e7996f81 100644 --- a/packages/content/src/index.ts +++ b/packages/content/src/index.ts @@ -5,4 +5,5 @@ export * from "./messages"; export * from "./notifications"; export * from "./relationships"; export * from "./requests"; +export * from "./tokens"; export * from "./ValidationErrorWithoutProperty"; diff --git a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts new file mode 100644 index 000000000..70ae791a3 --- /dev/null +++ b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts @@ -0,0 +1,45 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "../attributes/types/proprietary/ProprietaryAttributeValue"; + +export interface ITokenContentVerifiablePresentation extends ISerializable { + value: string | Record; + type: string; + displayInformation?: Record[]; +} + +@type("TokenContentVerifiablePresentation") +export class TokenContentVerifiablePresentation extends Serializable implements ITokenContentVerifiablePresentation { + @serialize({ any: true }) + @validate({ customValidator: validateValue }) + public value: string | Record; + + @serialize() + @validate({ nullable: true }) + public type: string; + + @serialize() + @validate({ nullable: true, max: PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH }) + public displayInformation?: Record[]; + + public static from(value: ITokenContentVerifiablePresentation): TokenContentVerifiablePresentation { + return this.fromAny(value); + } +} + +function validateValue(value: any) { + try { + const string = JSON.stringify(value); + // the length correspondes to 50MB - maybe this needs to be restricted further in the future + if (string.length > 52428800) { + return "stringified value must not be longer than 52428800 characters"; + } + } catch (e) { + if (e instanceof SyntaxError) { + return "must be a valid JSON object"; + } + + return "could not validate value"; + } + + return undefined; +} diff --git a/packages/content/src/tokens/index.ts b/packages/content/src/tokens/index.ts new file mode 100644 index 000000000..8420ddbe0 --- /dev/null +++ b/packages/content/src/tokens/index.ts @@ -0,0 +1 @@ +export * from "./TokenContentVerifiablePresentation"; diff --git a/packages/runtime/test/consumption/openid4vc.test.ts b/packages/runtime/test/consumption/openid4vc.test.ts index a98624603..7f70a459d 100644 --- a/packages/runtime/test/consumption/openid4vc.test.ts +++ b/packages/runtime/test/consumption/openid4vc.test.ts @@ -1,7 +1,7 @@ import { SdJwtVcRecord } from "@credo-ts/core"; import { EudiploClient } from "@eudiplo/sdk-core"; import { AcceptProposeAttributeRequestItemParametersWithNewAttributeJSON, AcceptShareAuthorizationRequestRequestItemParametersJSON, decodeRecord } from "@nmshd/consumption"; -import { RequestJSON, ShareAuthorizationRequestRequestItemJSON, VerifiableCredentialJSON, VerifiablePresentation } from "@nmshd/content"; +import { RequestJSON, ShareAuthorizationRequestRequestItemJSON, TokenContentVerifiablePresentation, VerifiableCredentialJSON } from "@nmshd/content"; import axios, { AxiosInstance } from "axios"; import * as client from "openid-client"; import path from "path"; @@ -280,9 +280,9 @@ describe("EUDIPLO", () => { const presentationTokenContent = presentationTokenResult.value.content; expect(presentationTokenContent).toBeDefined(); expect(presentationTokenContent["@type"]).toBe("VerifiablePresentation"); - expect((presentationTokenContent as VerifiablePresentation).value).toBeDefined(); - expect((presentationTokenContent as VerifiablePresentation).displayInformation).toBeDefined(); - expect((presentationTokenContent as VerifiablePresentation).displayInformation![0].name).toBe("test"); + expect((presentationTokenContent as TokenContentVerifiablePresentation).value).toBeDefined(); + expect((presentationTokenContent as TokenContentVerifiablePresentation).displayInformation).toBeDefined(); + expect((presentationTokenContent as TokenContentVerifiablePresentation).displayInformation![0].name).toBe("test"); }); }); From 098f0197dc96e00a17ef5f82f331424176f2a2bc Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 11:35:35 +0100 Subject: [PATCH 2/6] test: adjust expected type of token content --- packages/runtime/test/consumption/openid4vc.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/test/consumption/openid4vc.test.ts b/packages/runtime/test/consumption/openid4vc.test.ts index 7f70a459d..06bd3ec17 100644 --- a/packages/runtime/test/consumption/openid4vc.test.ts +++ b/packages/runtime/test/consumption/openid4vc.test.ts @@ -279,7 +279,7 @@ describe("EUDIPLO", () => { const presentationTokenContent = presentationTokenResult.value.content; expect(presentationTokenContent).toBeDefined(); - expect(presentationTokenContent["@type"]).toBe("VerifiablePresentation"); + expect(presentationTokenContent["@type"]).toBe("TokenContentVerifiablePresentation"); expect((presentationTokenContent as TokenContentVerifiablePresentation).value).toBeDefined(); expect((presentationTokenContent as TokenContentVerifiablePresentation).displayInformation).toBeDefined(); expect((presentationTokenContent as TokenContentVerifiablePresentation).displayInformation![0].name).toBe("test"); From 83c871864c6c56893ca5ccd6e2b2bb986520d988 Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 11:50:23 +0100 Subject: [PATCH 3/6] test: app-runtime test --- packages/app-runtime/test/runtime/AppStringProcessor.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts index a9d005554..8396618dd 100644 --- a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts +++ b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts @@ -1,4 +1,4 @@ -import { ArbitraryRelationshipTemplateContentJSON, AuthenticationRequestItem, RelationshipTemplateContent, VerifiablePresentation } from "@nmshd/content"; +import { ArbitraryRelationshipTemplateContentJSON, AuthenticationRequestItem, RelationshipTemplateContent, TokenContentVerifiablePresentation } from "@nmshd/content"; import { CoreDate, PasswordLocationIndicatorOptions } from "@nmshd/core-types"; import { DeviceOnboardingInfoDTO, PeerRelationshipTemplateLoadedEvent } from "@nmshd/runtime"; import assert from "assert"; @@ -379,7 +379,7 @@ describe("AppStringProcessor", function () { test("get a token with verifiable presentation content using a url", async function () { const tokenResult = await runtime1Session.transportServices.tokens.createOwnToken({ - content: VerifiablePresentation.from({ + content: TokenContentVerifiablePresentation.from({ value: { claim: "test" }, type: "dc+sd-jwt" }).toJSON(), From d9f4915f7b5c7d53c1221b45717cee1917f49d7c Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 14:06:41 +0100 Subject: [PATCH 4/6] chore: re-add TokenContentVerifiablePresentation --- .../tokens/TokenContentVerifiablePresentation.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts index 70ae791a3..57b9779bf 100644 --- a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts +++ b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts @@ -1,5 +1,13 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "../attributes/types/proprietary/ProprietaryAttributeValue"; +import { ContentJSON } from "../ContentJSON"; + +export interface TokenContentVerifiablePresentationJSON extends ContentJSON { + "@type": "TokenContentVerifiablePresentation"; + value: string | Record; + type: string; + displayInformation?: Record[]; +} export interface ITokenContentVerifiablePresentation extends ISerializable { value: string | Record; @@ -21,9 +29,13 @@ export class TokenContentVerifiablePresentation extends Serializable implements @validate({ nullable: true, max: PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH }) public displayInformation?: Record[]; - public static from(value: ITokenContentVerifiablePresentation): TokenContentVerifiablePresentation { + public static from(value: ITokenContentVerifiablePresentation | Omit): TokenContentVerifiablePresentation { return this.fromAny(value); } + + public override toJSON(verbose?: boolean | undefined, serializeAsString?: boolean | undefined): TokenContentVerifiablePresentationJSON { + return super.toJSON(verbose, serializeAsString) as TokenContentVerifiablePresentationJSON; + } } function validateValue(value: any) { From 38ec0122d04544afd0132ec89c114f475c552766 Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 14:06:55 +0100 Subject: [PATCH 5/6] chore: type --- .../content/src/tokens/TokenContentVerifiablePresentation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts index 57b9779bf..cd4c09311 100644 --- a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts +++ b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts @@ -41,7 +41,7 @@ export class TokenContentVerifiablePresentation extends Serializable implements function validateValue(value: any) { try { const string = JSON.stringify(value); - // the length correspondes to 50MB - maybe this needs to be restricted further in the future + // the length corresponds to 50MB - maybe this needs to be restricted further in the future if (string.length > 52428800) { return "stringified value must not be longer than 52428800 characters"; } From a684ae06ba205171261035ace779d357b43aadd9 Mon Sep 17 00:00:00 2001 From: Milena-Czierlinski Date: Thu, 12 Mar 2026 14:56:06 +0100 Subject: [PATCH 6/6] refactor: add ProprietaryAttributeValue to barrel file --- packages/content/src/attributes/RelationshipAttributeQuery.ts | 2 +- packages/content/src/attributes/types/VerifiableCredential.ts | 2 +- packages/content/src/attributes/types/proprietary/index.ts | 1 + .../content/src/tokens/TokenContentVerifiablePresentation.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/content/src/attributes/RelationshipAttributeQuery.ts b/packages/content/src/attributes/RelationshipAttributeQuery.ts index 5d82566d0..18af396dc 100644 --- a/packages/content/src/attributes/RelationshipAttributeQuery.ts +++ b/packages/content/src/attributes/RelationshipAttributeQuery.ts @@ -4,7 +4,7 @@ import { AbstractAttributeQuery, AbstractAttributeQueryJSON, IAbstractAttributeQ import { AttributeValues } from "./AttributeValueTypes"; import { IValueHints, ValueHints, ValueHintsJSON } from "./hints"; import { RelationshipAttributeConfidentiality } from "./RelationshipAttributeConfidentiality"; -import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH, PROPRIETARY_ATTRIBUTE_MAX_TITLE_LENGTH } from "./types/proprietary/ProprietaryAttributeValue"; +import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH, PROPRIETARY_ATTRIBUTE_MAX_TITLE_LENGTH } from "./types/proprietary"; export interface RelationshipAttributeCreationHintsJSON { title: string; diff --git a/packages/content/src/attributes/types/VerifiableCredential.ts b/packages/content/src/attributes/types/VerifiableCredential.ts index dee12cb09..3f9421641 100644 --- a/packages/content/src/attributes/types/VerifiableCredential.ts +++ b/packages/content/src/attributes/types/VerifiableCredential.ts @@ -1,7 +1,7 @@ import { serialize, type, validate } from "@js-soft/ts-serval"; import { AbstractAttributeValue, AbstractAttributeValueJSON, IAbstractAttributeValue } from "../AbstractAttributeValue"; import { RenderHints, RenderHintsEditType, RenderHintsTechnicalType, ValueHints } from "../hints"; -import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "./proprietary/ProprietaryAttributeValue"; +import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "./proprietary"; export interface VerifiableCredentialJSON extends AbstractAttributeValueJSON { "@type": "VerifiableCredential"; diff --git a/packages/content/src/attributes/types/proprietary/index.ts b/packages/content/src/attributes/types/proprietary/index.ts index 1fda99123..7735ade31 100644 --- a/packages/content/src/attributes/types/proprietary/index.ts +++ b/packages/content/src/attributes/types/proprietary/index.ts @@ -1,3 +1,4 @@ +export * from "./ProprietaryAttributeValue"; export * from "./ProprietaryBoolean"; export * from "./ProprietaryCountry"; export * from "./ProprietaryEMailAddress"; diff --git a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts index cd4c09311..c16ab844c 100644 --- a/packages/content/src/tokens/TokenContentVerifiablePresentation.ts +++ b/packages/content/src/tokens/TokenContentVerifiablePresentation.ts @@ -1,6 +1,6 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "../attributes/types/proprietary/ProprietaryAttributeValue"; import { ContentJSON } from "../ContentJSON"; +import { PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH } from "../attributes"; export interface TokenContentVerifiablePresentationJSON extends ContentJSON { "@type": "TokenContentVerifiablePresentation";