Skip to content

Commit 57b0ccf

Browse files
authored
Process token with VerifiablePresentation content over the app string processor (#984)
* chore: process token with verifiable presentation content * chore: run prettier * chore: add isTechnicallyValid param * chore: run audit fix * chore: pr comments
1 parent 00dfc1f commit 57b0ccf

7 files changed

Lines changed: 138 additions & 66 deletions

File tree

package-lock.json

Lines changed: 37 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app-runtime/src/AppStringProcessor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { OpenId4VciResolvedCredentialOffer } from "@credo-ts/openid4vc";
22
import { ILogger, ILoggerFactory } from "@js-soft/logging-abstractions";
33
import { Serializable } from "@js-soft/ts-serval";
44
import { EventBus, Result } from "@js-soft/ts-utils";
5+
import { VerifiablePresentation } from "@nmshd/content";
56
import { ICoreAddress, Reference } from "@nmshd/core-types";
67
import { AnonymousServices, DeviceMapper, RuntimeServices } from "@nmshd/runtime";
78
import { BackboneIds, TokenContentDeviceSharedSecret } from "@nmshd/transport";
@@ -289,6 +290,13 @@ export class AppStringProcessor {
289290
// RelationshipTemplates are processed by the RequestModule
290291
break;
291292
case "Token":
293+
const tokenContent = this.parseTokenContent(result.value.value.content);
294+
295+
if (tokenContent instanceof VerifiablePresentation) {
296+
// TODO: add technical validation
297+
await uiBridge.showVerifiablePresentation(account, result.value.value, true);
298+
break;
299+
}
292300
return Result.fail(AppRuntimeErrors.appStringProcessor.notSupportedTokenContent());
293301
case "DeviceOnboardingInfo":
294302
return Result.fail(AppRuntimeErrors.appStringProcessor.deviceOnboardingNotAllowed());

packages/app-runtime/src/extensibility/ui/IUIBridge.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { ApplicationError, Result } from "@js-soft/ts-utils";
22
import { OpenId4VciCredentialResponseJSON } from "@nmshd/consumption";
3-
import { DeviceOnboardingInfoDTO, FileDVO, IdentityDVO, LocalRequestDVO, MailDVO, MessageDVO, RequestMessageDVO, ResolveAuthorizationRequestResponse } from "@nmshd/runtime";
3+
import {
4+
DeviceOnboardingInfoDTO,
5+
FileDVO,
6+
IdentityDVO,
7+
LocalRequestDVO,
8+
MailDVO,
9+
MessageDVO,
10+
RequestMessageDVO,
11+
ResolveAuthorizationRequestResponse,
12+
TokenDTO
13+
} from "@nmshd/runtime";
414
import { LocalAccountDTO } from "../../multiAccount";
515

616
export interface IUIBridge {
717
showMessage(account: LocalAccountDTO, relationship: IdentityDVO, message: MessageDVO | MailDVO | RequestMessageDVO): Promise<Result<void>>;
818
showRelationship(account: LocalAccountDTO, relationship: IdentityDVO): Promise<Result<void>>;
919
showFile(account: LocalAccountDTO, file: FileDVO): Promise<Result<void>>;
20+
showVerifiablePresentation(account: LocalAccountDTO, token: TokenDTO, isTechnicallyValid: boolean): Promise<Result<void>>;
1021
showDeviceOnboarding(deviceOnboardingInfo: DeviceOnboardingInfoDTO): Promise<Result<void>>;
1122
showRequest(account: LocalAccountDTO, request: LocalRequestDVO): Promise<Result<void>>;
1223
showResolvedAuthorizationRequest(account: LocalAccountDTO, response: ResolveAuthorizationRequestResponse): Promise<Result<void>>;

packages/app-runtime/test/lib/FakeUIBridge.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class FakeUIBridge implements IUIBridge {
1414
return Promise.resolve(Result.ok(undefined));
1515
}
1616

17+
public showVerifiablePresentation(): Promise<Result<void, ApplicationError>> {
18+
return Promise.resolve(Result.ok(undefined));
19+
}
20+
1721
public showDeviceOnboarding(): Promise<Result<void, ApplicationError>> {
1822
return Promise.resolve(Result.ok(undefined));
1923
}

packages/app-runtime/test/lib/MockUIBridge.matchers.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,44 @@ expect.extend({
214214

215215
return { pass: true, message: () => "" };
216216
},
217+
showVerifiablePresentationCalled(mockUIBridge: unknown, id: string, isTechnicallyValid: boolean) {
218+
if (!(mockUIBridge instanceof MockUIBridge)) {
219+
throw new Error("This method can only be used with expect(MockUIBridge).");
220+
}
221+
222+
const calls = mockUIBridge.calls.filter((x) => x.method === "showVerifiablePresentation");
223+
if (calls.length === 0) {
224+
return { pass: false, message: () => "The method showVerifiablePresentation was not called." };
225+
}
226+
227+
const matchingCalls = calls.filter((x) => x.token.id === id && x.isTechnicallyValid === isTechnicallyValid);
228+
if (matchingCalls.length === 0) {
229+
const callsWithData = calls.map((e) => `'${e.token.id}' (isTechnicallyValid: ${e.isTechnicallyValid})`).join(", ");
230+
return {
231+
pass: false,
232+
message: () =>
233+
`The method showVerifiablePresentation was called, but not with token id '${id}' and isTechnicallyValid '${isTechnicallyValid}', instead with calls ${callsWithData}.`
234+
};
235+
}
236+
237+
return { pass: true, message: () => "" };
238+
},
239+
showVerifiablePresentationNotCalled(mockUIBridge: unknown) {
240+
if (!(mockUIBridge instanceof MockUIBridge)) {
241+
throw new Error("This method can only be used with expect(MockUIBridge).");
242+
}
243+
244+
const calls = mockUIBridge.calls.filter((x) => x.method === "showVerifiablePresentation");
245+
if (calls.length > 0) {
246+
return {
247+
pass: false,
248+
message: () =>
249+
`The method showVerifiablePresentation called: ${calls.map((c) => `'account id: ${c.account.id} - tokenId: ${c.token.id} - isTechnicallyValid: ${c.isTechnicallyValid}'`)}`
250+
};
251+
}
252+
253+
return { pass: true, message: () => "" };
254+
},
217255
showErrorCalled(mockUIBridge: unknown, code: string) {
218256
if (!(mockUIBridge instanceof MockUIBridge)) {
219257
throw new Error("This method can only be used with expect(MockUIBridge).");
@@ -253,6 +291,8 @@ declare global {
253291
showResolvedCredentialOfferNotCalled(): R;
254292
showFileCalled(id: string): R;
255293
showFileNotCalled(): R;
294+
showVerifiablePresentationCalled(id: string, isTechnicallyValid: boolean): R;
295+
showVerifiablePresentationNotCalled(): R;
256296
showErrorCalled(code: string): R;
257297
}
258298
}

packages/app-runtime/test/lib/MockUIBridge.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { ApplicationError, Result } from "@js-soft/ts-utils";
22
import { OpenId4VciCredentialResponseJSON } from "@nmshd/consumption";
3-
import { DeviceOnboardingInfoDTO, FileDVO, IdentityDVO, LocalRequestDVO, MailDVO, MessageDVO, RequestMessageDVO, ResolveAuthorizationRequestResponse } from "@nmshd/runtime";
3+
import {
4+
DeviceOnboardingInfoDTO,
5+
FileDVO,
6+
IdentityDVO,
7+
LocalRequestDVO,
8+
MailDVO,
9+
MessageDVO,
10+
RequestMessageDVO,
11+
ResolveAuthorizationRequestResponse,
12+
TokenDTO
13+
} from "@nmshd/runtime";
414
import { IUIBridge, LocalAccountDTO } from "../../src";
515

616
export type MockUIBridgeCall =
717
| { method: "showMessage"; account: LocalAccountDTO; relationship: IdentityDVO; message: MessageDVO | MailDVO | RequestMessageDVO }
818
| { method: "showRelationship"; account: LocalAccountDTO; relationship: IdentityDVO }
919
| { method: "showFile"; account: LocalAccountDTO; file: FileDVO }
20+
| { method: "showVerifiablePresentation"; account: LocalAccountDTO; token: TokenDTO; isTechnicallyValid: boolean }
1021
| { method: "showDeviceOnboarding"; deviceOnboardingInfo: DeviceOnboardingInfoDTO }
1122
| { method: "showRequest"; account: LocalAccountDTO; request: LocalRequestDVO }
1223
| { method: "showResolvedAuthorizationRequest"; account: LocalAccountDTO; response: ResolveAuthorizationRequestResponse }
@@ -57,6 +68,12 @@ export class MockUIBridge implements IUIBridge {
5768
return Promise.resolve(Result.ok(undefined));
5869
}
5970

71+
public showVerifiablePresentation(account: LocalAccountDTO, token: TokenDTO, isTechnicallyValid: boolean): Promise<Result<void>> {
72+
this._calls.push({ method: "showVerifiablePresentation", account, token, isTechnicallyValid });
73+
74+
return Promise.resolve(Result.ok(undefined));
75+
}
76+
6077
public showDeviceOnboarding(deviceOnboardingInfo: DeviceOnboardingInfoDTO): Promise<Result<void>> {
6178
this._calls.push({ method: "showDeviceOnboarding", deviceOnboardingInfo });
6279

packages/app-runtime/test/runtime/AppStringProcessor.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ArbitraryRelationshipTemplateContentJSON, AuthenticationRequestItem, RelationshipTemplateContent } from "@nmshd/content";
1+
import { ArbitraryRelationshipTemplateContentJSON, AuthenticationRequestItem, RelationshipTemplateContent, VerifiablePresentation } from "@nmshd/content";
22
import { CoreDate, PasswordLocationIndicatorOptions } from "@nmshd/core-types";
33
import { DeviceOnboardingInfoDTO, PeerRelationshipTemplateLoadedEvent } from "@nmshd/runtime";
44
import assert from "assert";
@@ -377,6 +377,24 @@ describe("AppStringProcessor", function () {
377377
expect(runtime4MockUiBridge).showFileCalled(file.id);
378378
});
379379

380+
test("get a token with verifiable presentation content using a url", async function () {
381+
const tokenResult = await runtime1Session.transportServices.tokens.createOwnToken({
382+
content: VerifiablePresentation.from({
383+
value: { claim: "test" },
384+
type: "dc+sd-jwt"
385+
}).toJSON(),
386+
expiresAt: CoreDate.utc().add({ days: 1 }).toISOString(),
387+
ephemeral: true
388+
});
389+
const token = tokenResult.value;
390+
391+
const result = await runtime4.stringProcessor.processURL(token.reference.url, runtime4Session.account);
392+
expect(result).toBeSuccessful();
393+
expect(result.value).toBeUndefined();
394+
395+
expect(runtime4MockUiBridge).showVerifiablePresentationCalled(token.id, true);
396+
});
397+
380398
test("get a template using a url", async function () {
381399
const templateResult = await runtime1Session.transportServices.relationshipTemplates.createOwnRelationshipTemplate({
382400
content: RelationshipTemplateContent.from({

0 commit comments

Comments
 (0)