Skip to content

Inital merge of demo-relevant oid4vc features into main#978

Draft
Magnus-Kuhn wants to merge 20 commits intomainfrom
initial-oid4vc-merge
Draft

Inital merge of demo-relevant oid4vc features into main#978
Magnus-Kuhn wants to merge 20 commits intomainfrom
initial-oid4vc-merge

Conversation

@Magnus-Kuhn
Copy link
Copy Markdown
Contributor

@Magnus-Kuhn Magnus-Kuhn commented Feb 27, 2026

Readiness checklist

  • I added/updated tests.
  • I ensured that the PR title is good enough for the changelog.
  • I labeled the PR.
  • I self-reviewed the PR.

Description

Transferred are

  • the entire credo tooling (KMS etc.)
  • sd-jwt-dc as only acceptable credential format
  • dcql and pex as query languages (pex because it's still used in some demos)
  • ShareCredentialOfferRequestItem
  • UseCases for authorization requests

The presentation token is not transferred.

@Magnus-Kuhn Magnus-Kuhn added the wip Work in Progress (blocks mergify from auto update the branch) label Mar 18, 2026
Magnus-Kuhn and others added 12 commits March 27, 2026 16:29
… processor (#984)

* chore: process token with verifiable presentation content

* chore: run prettier

* chore: add isTechnicallyValid param

* chore: run audit fix

* chore: pr comments
…ePresentation (#992)

* chore: npm audit fix

* test: displayInformation is transferred in PresentationToken

* fix: explicitly hand over type and displayInformation when creating VerifiablePresentation
…#994)

* feat: expose expiresAt and ephemeral when creating a presentation token

* refactor: specify type of input string

* chore: rebuild schemas
…993)

* refactor: make VerifiablePresentation to TokenContentVerifiablePresentation

* test: adjust expected type of token content

* test: app-runtime test

* chore: re-add TokenContentVerifiablePresentation

* chore: type

* refactor: add ProprietaryAttributeValue to barrel file
* feat: use token id as nonce

* feat: use ephemeral for empty token

* fix: use string

* chore: npm audit fix

* chore: exclude vulnerabilities

---------

Co-authored-by: Timo Notheisen <timo.notheien@js-soft.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
@Magnus-Kuhn Magnus-Kuhn added the enhancement New feature or request label Mar 27, 2026
@erbenjak erbenjak marked this pull request as ready for review April 22, 2026 11:29
Copilot AI review requested due to automatic review settings April 22, 2026 11:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR merges a set of demo-focused OID4VC/OID4VP capabilities into the main codebase, including OpenID4VC consumption flows, a new request item type for sharing credential offers, runtime/app-runtime UI support, and test/docker tooling to exercise end-to-end demo scenarios.

Changes:

  • Introduces an OpenID4VC consumption controller + runtime use-cases/facade for resolving/accepting authorization requests and creating presentation tokens.
  • Adds ShareCredentialOfferRequestItem end-to-end (content model, consumption processor + eventing, runtime DVO expansion, schemas).
  • Updates Jest/TS configs and adds dev docker-compose + assets to run OID4VC demo tests locally/CI, plus a patch-package fix for case-insensitive hostname matching in OID4VP.

Reviewed changes

Copilot reviewed 74 out of 76 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
patches/@openid4vc+openid4vp+0.4.0+001+initial.patch Patch-package tweak: case-insensitive hostname comparison for OID4VP client id validation
packages/tsconfig.base.json Enables skipLibCheck globally (flagged as PoC)
packages/runtime/test/tsconfig.json Allows JS compilation in tests (ESM/CJS interop for Jest)
packages/runtime/test/lib/RuntimeServiceProvider.ts Adds injectable fetchInstance for tests
packages/runtime/test/customMatchers.ts Improves toBeSuccessful failure messages with error code + optional data
packages/runtime/test/consumption/openid4vc.test.ts Adds OID4VC end-to-end tests using Docker Compose + Eudiplo
packages/runtime/src/useCases/consumption/openid4vc/index.ts Exports new OID4VC consumption use-cases
packages/runtime/src/useCases/consumption/openid4vc/ResolveAuthorizationRequest.ts Use-case to resolve OID4VP auth requests and map matching credentials
packages/runtime/src/useCases/consumption/openid4vc/CreatePresentationToken.ts Use-case to create token content for verifiable presentation tokens
packages/runtime/src/useCases/consumption/openid4vc/AcceptAuthorizationRequest.ts Use-case to submit an authorization request response using a selected credential
packages/runtime/src/useCases/consumption/index.ts Re-exports new openid4vc use-cases
packages/runtime/src/useCases/common/Schemas.ts Adds schemas for new requests/types (generated output)
packages/runtime/src/extensibility/facades/consumption/index.ts Exports the new OpenId4VcFacade
packages/runtime/src/extensibility/facades/consumption/OpenId4VcFacade.ts Facade for new OID4VC-related use-cases
packages/runtime/src/extensibility/ConsumptionServices.ts Adds openId4Vc facade to consumption services
packages/runtime/src/events/EventProxy.ts Proxies new “credential offer item processed” consumption event to runtime-types event
packages/runtime/src/dataViews/content/RequestItemDVOs.ts Adds DVO type for ShareCredentialOfferRequestItem including credential responses
packages/runtime/src/dataViews/DataViewExpander.ts Expands ShareCredentialOfferRequestItem by fetching/caching credential responses
packages/runtime/src/Runtime.ts Binds OpenId4VcController into the IoC container
packages/runtime/package.json Jest transforms updated; adds dependencies/devDependencies for OID4VC demo tooling
packages/runtime-types/src/events/consumption/index.ts Exports new runtime-types consumption event
packages/runtime-types/src/events/consumption/ShareCredentialOfferRequestItemProcessedByRecipientEvent.ts Adds runtime-types event for “credential offer request item processed”
packages/content/src/tokens/index.ts Exports token content type for verifiable presentations
packages/content/src/tokens/TokenContentVerifiablePresentation.ts Adds token content type used for verifiable presentation tokens
packages/content/src/requests/items/shareCredentialOffer/ShareCredentialOfferRequestItem.ts Adds new request item type for sharing a credential offer URL
packages/content/src/requests/items/index.ts Exports the new request item
packages/content/src/requests/RequestItem.ts Adds the new request item to request item derivation unions/guards
packages/content/src/index.ts Re-exports tokens module
packages/content/src/attributes/types/proprietary/index.ts Re-exports ProprietaryAttributeValue from directory index
packages/content/src/attributes/types/index.ts Exports new VerifiableCredential attribute value
packages/content/src/attributes/types/VerifiableCredential.ts Adds VerifiableCredential attribute value type (for VC storage/display)
packages/content/src/attributes/RelationshipAttributeQuery.ts Adjusts proprietary constants import path
packages/content/src/attributes/AttributeValueTypes.ts Adds VerifiableCredential to AttributeValues unions + class registry
packages/consumption/test/tsconfig.json Allows JS compilation in tests (ESM/CJS interop for Jest)
packages/consumption/src/modules/requests/outgoing/OutgoingRequestsController.ts Publishes an event returned by item processors when applying incoming responses
packages/consumption/src/modules/requests/itemProcessors/shareCredentialOffer/ShareCredentialOfferRequestItemProcessor.ts Implements request item validation, acceptance, storage, and event emission
packages/consumption/src/modules/requests/index.ts Exports the new request item processor
packages/consumption/src/modules/requests/events/index.ts Exports new consumption event
packages/consumption/src/modules/requests/events/ShareCredentialOfferRequestItemProcessedByRecipientEvent.ts Adds consumption-layer event for processed credential offer item
packages/consumption/src/modules/openid4vc/local/RequestedCredentialCache.ts Adds synchronized cache for credential responses keyed by offer URL
packages/consumption/src/modules/openid4vc/local/OpenId4VciCredentialResponseJSON.ts Adds local JSON type for credential responses
packages/consumption/src/modules/openid4vc/local/KeyStorage.ts Adds synchronized key storage for the local KMS backend
packages/consumption/src/modules/openid4vc/local/Holder.ts Adds Credo-based holder agent wrapper (resolve offers, request/store creds, accept auth requests, create VP token content)
packages/consumption/src/modules/openid4vc/local/EnmeshedStorageService.ts Storage bridge: stores resolved credentials as OwnIdentityAttributes; query/load mapping
packages/consumption/src/modules/openid4vc/local/EnmeshedHolderKeyManagmentService.ts Adds custom KMS backend for signing/encryption (EdDSA/ES256 + ECDH-ES/AES-GCM)
packages/consumption/src/modules/openid4vc/local/EnmeshedHolderFileSystem.ts Stub FileSystem implementation for Credo dependencies
packages/consumption/src/modules/openid4vc/local/BaseAgent.ts Base agent wrapper configuring Credo dependencies (storage/KMS/ws/fetch)
packages/consumption/src/modules/openid4vc/index.ts Exports the new openid4vc module/controller pieces
packages/consumption/src/modules/openid4vc/OpenId4VcController.ts Adds consumption controller for OID4VC/OID4VP flows and credential matching
packages/consumption/src/modules/index.ts Re-exports openid4vc module
packages/consumption/src/consumption/ConsumptionIds.ts Adds ID helper for requested credential cache entries
packages/consumption/src/consumption/ConsumptionControllerName.ts Adds controller name enum for OpenId4VcController
packages/consumption/src/consumption/ConsumptionController.ts Initializes OpenId4VcController and registers new request item processor
packages/consumption/src/consumption/ConsumptionConfig.ts Adds optional fetchInstance override
packages/consumption/package.json Updates Jest transforms; adds OID4VC dependencies (but currently incomplete)
packages/app-runtime/test/tsconfig.json Allows JS compilation in tests (ESM/CJS interop for Jest)
packages/app-runtime/test/runtime/AppStringProcessor.test.ts Adds test for processing a token containing verifiable presentation content
packages/app-runtime/test/lib/MockUIBridge.ts Adds UI bridge methods for showing VP tokens and resolved auth requests
packages/app-runtime/test/lib/MockUIBridge.matchers.ts Adds matchers for new UI bridge calls (plus extra declared-but-missing matchers)
packages/app-runtime/test/lib/FakeUIBridge.ts Adds stub methods for new UI bridge calls
packages/app-runtime/src/extensibility/ui/IUIBridge.ts Extends UI bridge with VP display + resolved OID4VP request display
packages/app-runtime/src/AppStringProcessor.ts Adds openid4vp: URL support + VP token content handling
packages/app-runtime/package.json Updates Jest transforms/ignore patterns similar to other packages
package.json Adds postinstall patch-package and docker scripts for OID4VC stack
.github/workflows/test.yml Logs into GHCR before running tests to pull OID4VC demo images
.dev/oid4vc-service-config.json Adds local OID4VC service config for the dev compose stack
.dev/eudiplo-assets/** Adds Eudiplo demo assets (tenant config, keys, certs, logo, presentation/issuance configs)
.dev/compose.openid4vc.yml Adds docker-compose stack for OID4VC service + connector + eudiplo + mongodb

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +28
public override async init(): Promise<this> {
const keyCollection = await this.parent.accountController.getSynchronizedCollection("openid4vc-keys");
const keyStorage = new KeyStorage(keyCollection, this._log);

this.holder = new Holder(keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
await this.holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");

const requestedCredentialsCacheCollection = await this.parent.accountController.getSynchronizedCollection("openid4vc-requested-credentials-cache");
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

A fixed private key string is hard-coded into initializeAgent(...). This effectively makes every runtime instance share the same long-lived key material and leaks it to anyone reading the source. Please load this from configuration/secure storage (or derive per-account keys) and fail initialization if no key material is provided.

Copilot uses AI. Check for mistakes.
Comment on lines 77 to 103
@@ -67,15 +85,20 @@
"@nmshd/core-types": "*",
"@nmshd/iql": "^1.0.5",
"@nmshd/transport": "*",
"@noble/ciphers": "^2.0.1",
"jose": "^6.1.1",
"lodash": "^4.18.1",
"ts-simple-nameof": "^1.3.3"
"sjcl": "^1.0.8",
"ts-simple-nameof": "^1.3.3",
"ws": "^8.18.3"
},
"devDependencies": {
"@js-soft/docdb-access-loki": "1.4.1",
"@js-soft/docdb-access-mongo": "1.4.1",
"@js-soft/node-logger": "1.2.4",
"@nmshd/crypto": "2.1.3",
"@types/lodash": "^4.17.24",
"@types/sjcl": "^1.0.34",
"ts-mockito": "^2.6.1"
},
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This file is used by the @nmshd/consumption runtime code, but elliptic is not listed in dependencies even though it is imported by EnmeshedHolderKeyManagmentService. Add elliptic (and move @nmshd/crypto from devDependencies to dependencies, since it is also imported at runtime) to avoid "module not found" for consumers installing @nmshd/consumption directly.

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +148
const server = URL.parse("https://kc-openid4vc.is.enmeshed.eu/realms/enmeshed-openid4vci")!;
const clientId = "wallet";
const config: client.Configuration = await client.discovery(server, clientId);
const grantReq = await client.genericGrantRequest(config, "password", {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

URL.parse(...) is not part of the WHATWG URL API and will throw at runtime (and usually fails type-checking). Use new URL("…") (or import parse from node:url if you need legacy parsing) before passing the issuer URL to openid-client discovery.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +57
// the length correspondes to 50MB - maybe this needs to be restricted further in the future
if (string.length > 52428800) {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

Typo in comment: "correspondes" should be "corresponds".

Copilot uses AI. Check for mistakes.

this._settings = await new SettingsController(this).init();

this._openId4Vc = await new OpenId4VcController(this).init();
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

OpenId4VcController is initialized unconditionally during ConsumptionController.init(). Given that initialization spins up an agent, KMS backend, key storage collections, etc., this adds startup cost and risk for runtimes that don't need OID4VC. Consider guarding this behind an explicit feature flag in ConsumptionConfig (and/or lazy-initializing on first use).

Suggested change
this._openId4Vc = await new OpenId4VcController(this).init();
const openId4VcEnabled = (this.consumptionConfig as ConsumptionConfig & { enableOpenId4Vc?: boolean }).enableOpenId4Vc !== false;
if (openId4VcEnabled) {
this._openId4Vc = await new OpenId4VcController(this).init();
}

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +28
@serialize()
@validate({ nullable: true })
public type: string;

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The type property is required by IVerifiableCredential/VerifiableCredentialJSON, but the validator allows it to be null (@validate({ nullable: true })). This can let invalid credential objects pass validation and diverges from the schema expectations. Consider removing nullable: true (or making type optional everywhere if it truly can be missing).

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +40
try {
await this.consumptionController.openId4Vc.requestAllCredentialsFromCredentialOfferUrl(requestItem.credentialOfferUrl);
return ValidationResult.success();
} catch (error) {
return ValidationResult.error(
ConsumptionCoreErrors.requests.invalidRequestItem(`The credential offer at URL '${requestItem.credentialOfferUrl}' could not be processed. Cause: ${error}`)
);
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The error message interpolates the caught error object directly (Cause: ${error}), which often results in [object Object] and may accidentally include sensitive data. Prefer extracting a safe message (e.g., error instanceof Error ? error.message : String(error)) and consider logging the full error separately.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +43
const result = await this.openId4VcController.acceptAuthorizationRequest(request.authorizationRequest, credential);
return Result.ok({ status: result.status, message: JSON.stringify(result.message) });
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

JSON.stringify(result.message) will wrap an already-string message in quotes (e.g. "ok"), which is probably not what API consumers expect in AcceptAuthorizationRequestResponse.message. Consider only stringifying when result.message is not already a string.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +268
showResolvedCredentialOfferCalled(): R;
showResolvedCredentialOfferNotCalled(): R;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The Jest matcher typings declare showResolvedCredentialOfferCalled/NotCalled, but there are no corresponding implementations in expect.extend(...). This will compile but fail at runtime if used. Either implement these matchers or remove them from the Matchers interface until they exist.

Suggested change
showResolvedCredentialOfferCalled(): R;
showResolvedCredentialOfferNotCalled(): R;

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +27
@serialize()
@validate({ nullable: true })
public type: string;

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The type field is required by TokenContentVerifiablePresentationJSON, but the validator currently allows null (@validate({ nullable: true })). This can create tokens that serialize as invalid JSON for consumers expecting type: string. Consider making it non-nullable (or consistently optional across the type + schema).

Copilot uses AI. Check for mistakes.
@erbenjak erbenjak marked this pull request as draft April 22, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request wip Work in Progress (blocks mergify from auto update the branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants