diff --git a/packages/app-runtime/test/lib/MockUIBridge.matchers.ts b/packages/app-runtime/test/lib/MockUIBridge.matchers.ts index 6802eaecd..3c3a868b2 100644 --- a/packages/app-runtime/test/lib/MockUIBridge.matchers.ts +++ b/packages/app-runtime/test/lib/MockUIBridge.matchers.ts @@ -1,7 +1,8 @@ +import { DeviceOnboardingInfoDTO } from "@nmshd/runtime"; import { MockUIBridge } from "./MockUIBridge"; expect.extend({ - showDeviceOnboardingCalled(mockUIBridge: unknown, deviceId: string) { + showDeviceOnboardingCalled(mockUIBridge: unknown, predicate: (deviceOnboardingInfo: DeviceOnboardingInfoDTO) => boolean) { if (!(mockUIBridge instanceof MockUIBridge)) { throw new Error("This method can only be used with expect(MockUIBridge)."); } @@ -11,12 +12,12 @@ expect.extend({ return { pass: false, message: () => "The method showDeviceOnboarding was not called." }; } - const matchingCalls = calls.filter((x) => x.deviceOnboardingInfo.id === deviceId); + const matchingCalls = calls.filter((x) => predicate(x.deviceOnboardingInfo)); if (matchingCalls.length === 0) { return { pass: false, message: () => - `The method showDeviceOnboarding was called, but not with the specified device id '${deviceId}', instead with ids '${calls.map((e) => e.deviceOnboardingInfo.id).join(", ")}'.` + `The method showDeviceOnboarding was called, but not with the specified predicate, instead with payloads '${calls.map((e) => JSON.stringify(e.deviceOnboardingInfo)).join(", ")}'.` }; } @@ -112,7 +113,7 @@ expect.extend({ declare global { namespace jest { interface Matchers { - showDeviceOnboardingCalled(deviceId: string): R; + showDeviceOnboardingCalled(predicate: (deviceOnboardingInfo: DeviceOnboardingInfoDTO) => boolean): R; showDeviceOnboardingNotCalled(): R; requestAccountSelectionCalled(possibleAccountsLength: number): R; requestAccountSelectionNotCalled(): R; diff --git a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts index 5918dfb18..62f11370b 100644 --- a/packages/app-runtime/test/runtime/AppStringProcessor.test.ts +++ b/packages/app-runtime/test/runtime/AppStringProcessor.test.ts @@ -1,6 +1,6 @@ import { ArbitraryRelationshipTemplateContentJSON } from "@nmshd/content"; import { CoreDate } from "@nmshd/core-types"; -import { PeerRelationshipTemplateLoadedEvent } from "@nmshd/runtime"; +import { DeviceOnboardingInfoDTO, PeerRelationshipTemplateLoadedEvent } from "@nmshd/runtime"; import assert from "assert"; import { AppRuntime, LocalAccountSession } from "../../src"; import { MockEventBus, MockUIBridge, TestUtil } from "../lib"; @@ -216,7 +216,22 @@ describe("AppStringProcessor", function () { expect(result).toBeSuccessful(); expect(result.value).toBeUndefined(); - expect(mockUiBridge).showDeviceOnboardingCalled(deviceResult.value.id); + expect(mockUiBridge).showDeviceOnboardingCalled((deviceOnboardingInfo: DeviceOnboardingInfoDTO) => deviceOnboardingInfo.id === deviceResult.value.id); + }); + + test("backup device onboarding with a password protected Token", async function () { + const tokenResult = await runtime1Session.transportServices.identityRecoveryKits.createIdentityRecoveryKit({ + profileName: "profileNameForBackupDevice", + passwordProtection: { password: "password" } + }); + + mockUiBridge.setPasswordToReturnForAttempt(1, "password"); + + const result = await runtime2.stringProcessor.processTruncatedReference(tokenResult.value.truncatedReference); + expect(result).toBeSuccessful(); + expect(result.value).toBeUndefined(); + + expect(mockUiBridge).showDeviceOnboardingCalled((deviceOnboardingInfo: DeviceOnboardingInfoDTO) => deviceOnboardingInfo.isBackupDevice === true); }); }); }); diff --git a/packages/runtime/src/types/transport/DeviceOnboardingInfoDTO.ts b/packages/runtime/src/types/transport/DeviceOnboardingInfoDTO.ts index 12a3d8b25..e8fe42f57 100644 --- a/packages/runtime/src/types/transport/DeviceOnboardingInfoDTO.ts +++ b/packages/runtime/src/types/transport/DeviceOnboardingInfoDTO.ts @@ -14,4 +14,5 @@ export interface DeviceOnboardingInfoDTO { identity: IdentityDTO; password: string; username: string; + isBackupDevice?: boolean; } diff --git a/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts b/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts index d7971117c..73ded9e7b 100644 --- a/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts +++ b/packages/runtime/src/useCases/transport/devices/DeviceMapper.ts @@ -41,7 +41,8 @@ export class DeviceMapper { }, password: deviceSharedSecret.password, username: deviceSharedSecret.username, - profileName: deviceSharedSecret.profileName + profileName: deviceSharedSecret.profileName, + isBackupDevice: deviceSharedSecret.isBackupDevice }; } @@ -62,7 +63,8 @@ export class DeviceMapper { }, password: deviceOnboardingDTO.password, username: deviceOnboardingDTO.username, - profileName: deviceOnboardingDTO.profileName + profileName: deviceOnboardingDTO.profileName, + isBackupDevice: deviceOnboardingDTO.isBackupDevice }); return sharedSecret; } diff --git a/packages/transport/src/modules/devices/DeviceSecretController.ts b/packages/transport/src/modules/devices/DeviceSecretController.ts index 2721aadac..86a26ff09 100644 --- a/packages/transport/src/modules/devices/DeviceSecretController.ts +++ b/packages/transport/src/modules/devices/DeviceSecretController.ts @@ -142,7 +142,8 @@ export class DeviceSecretController extends TransportController { identityPrivateKey: identityPrivateKey?.secret as CryptoSignaturePrivateKey, username: device.username, password: device.initialPassword!, - identity: this.parent.identity.identity + identity: this.parent.identity.identity, + isBackupDevice: device.isBackupDevice }); return deviceSharedSecret; diff --git a/packages/transport/src/modules/devices/transmission/DeviceSharedSecret.ts b/packages/transport/src/modules/devices/transmission/DeviceSharedSecret.ts index 2bc29623c..68456281f 100644 --- a/packages/transport/src/modules/devices/transmission/DeviceSharedSecret.ts +++ b/packages/transport/src/modules/devices/transmission/DeviceSharedSecret.ts @@ -17,6 +17,7 @@ export interface IDeviceSharedSecret extends ISerializable { identity: IIdentity; password: string; username: string; + isBackupDevice?: boolean; } @type("DeviceSharedSecret") @@ -73,6 +74,10 @@ export class DeviceSharedSecret extends Serializable implements IDeviceSharedSec @validate() public password: string; + @serialize() + @validate({ nullable: true }) + public isBackupDevice?: boolean; + public static from(value: IDeviceSharedSecret): DeviceSharedSecret { return this.fromAny(value); }