From 736392dc1582087b98436c77b0c54fe5d022298e Mon Sep 17 00:00:00 2001 From: Frederik Rothenberger Date: Thu, 7 Mar 2024 10:35:20 +0100 Subject: [PATCH] Fix dead-end in device add flow (#2338) This PR fixes a dead end where users would end up stuck after adding a new passkey to an identity from the sign-in screen. Note: This PR does not yet include the new confirmation screens as per figma. This will be done separately. --- .../src/components/authenticateBox.ts | 32 ++++++++++--------- src/frontend/src/test-e2e/addDevice.test.ts | 9 ++++++ src/frontend/src/test-e2e/views.ts | 4 +++ src/showcase/src/flows.ts | 5 ++- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/frontend/src/components/authenticateBox.ts b/src/frontend/src/components/authenticateBox.ts index 2de66b9069..8bb7499dfb 100644 --- a/src/frontend/src/components/authenticateBox.ts +++ b/src/frontend/src/components/authenticateBox.ts @@ -105,9 +105,9 @@ export const authenticateBox = async ({ for (;;) { const result = await promptAuth(); - // If the user canceled, we retry + // If the user canceled or just added a device, we retry if ("tag" in result) { - result satisfies { tag: "canceled" }; + result satisfies { tag: "canceled" | "deviceAdded" }; continue; } @@ -158,7 +158,9 @@ export const authenticateBoxFlow = async ({ }: { i18n: I18n; templates: AuthnTemplates; - addDevice: (userNumber?: bigint) => Promise<{ alias: string }>; + addDevice: ( + userNumber?: bigint + ) => Promise<{ alias: string; userNumber: bigint }>; loginPasskey: ( userNumber: bigint ) => Promise< @@ -192,6 +194,7 @@ export const authenticateBoxFlow = async ({ }) | FlowError | { tag: "canceled" } + | { tag: "deviceAdded" } > => { const pages = authnScreens(i18n, { ...templates }); @@ -243,6 +246,7 @@ export const authenticateBoxFlow = async ({ }) | FlowError | { tag: "canceled" } + | { tag: "deviceAdded" } > => { const result = await pages.useExisting(); if (result.tag === "submit") { @@ -250,13 +254,10 @@ export const authenticateBoxFlow = async ({ } if (result.tag === "add_device") { - const _ = await addDevice(result.userNumber); - // XXX: we don't currently do anything with the result from adding a device and - // we let the flow hang. - const hang = await new Promise((_) => { - /* hang forever */ - }); - return hang; + const { userNumber } = await addDevice(result.userNumber); + // The user number now has a passkey associated and hence should be remembered + await setAnchorUsed(userNumber); + return { tag: "deviceAdded" } as const; } if (result.tag === "register") { @@ -654,7 +655,7 @@ const loginPinIdentityMaterial = ({ const asNewDevice = async ( connection: Connection, prefilledUserNumber?: bigint -) => { +): Promise<{ alias: string; userNumber: bigint }> => { // Prompt the user for an anchor and provide additional information about the flow. // If the user number is already known, it is prefilled in the screen. const promptUserNumberWithInfo = async (prefilledUserNumber?: bigint) => { @@ -672,10 +673,11 @@ const asNewDevice = async ( return result; }; - return registerTentativeDevice( - await promptUserNumberWithInfo(prefilledUserNumber), - connection - ); + const userNumber = await promptUserNumberWithInfo(prefilledUserNumber); + return { + userNumber, + ...(await registerTentativeDevice(userNumber, connection)), + }; }; // Helper to convert PIN identity material to a Der public-key diff --git a/src/frontend/src/test-e2e/addDevice.test.ts b/src/frontend/src/test-e2e/addDevice.test.ts index e7707aed79..b549e7a06a 100644 --- a/src/frontend/src/test-e2e/addDevice.test.ts +++ b/src/frontend/src/test-e2e/addDevice.test.ts @@ -10,6 +10,7 @@ import { AddIdentityAnchorView, AddRemoteDeviceInstructionsView, AddRemoteDeviceVerificationCodeView, + AuthenticateView, MainView, NotInRegistrationModeView, VerifyRemoteDeviceView, @@ -151,6 +152,14 @@ test("Add remote device starting on new device", async () => { const addDeviceSuccessView = new AddDeviceSuccessView(browser); await addDeviceSuccessView.waitForDisplay(); await addDeviceSuccessView.continue(); + + // browser 2 again + // make sure the browser now shows the sign-in screen with the user number + // pre-filled + await focusBrowser(browser2); + const authView = new AuthenticateView(browser2); + await authView.waitForDisplay(); + await authView.expectAnchor(userNumber); }); await mainView.waitForDisplay(); diff --git a/src/frontend/src/test-e2e/views.ts b/src/frontend/src/test-e2e/views.ts index 9588dc3108..a77198deda 100644 --- a/src/frontend/src/test-e2e/views.ts +++ b/src/frontend/src/test-e2e/views.ts @@ -495,6 +495,10 @@ export class AuthenticateView extends View { await this.browser.$(`[data-anchor-id="${anchor}"]`).click(); } + async expectAnchor(anchor: string): Promise { + await this.browser.$(`[data-anchor-id="${anchor}"]`).waitForDisplayed(); + } + async expectAnchorInputField(): Promise { await this.browser .$('[data-role="anchor-input"]') diff --git a/src/showcase/src/flows.ts b/src/showcase/src/flows.ts index a4d4bc4032..ce61bc966a 100644 --- a/src/showcase/src/flows.ts +++ b/src/showcase/src/flows.ts @@ -57,7 +57,10 @@ export const iiFlows: Record void> = { templates: manageTemplates, addDevice: () => { toast.info(html`Added device`); - return Promise.resolve({ alias: "My Device" }); + return Promise.resolve({ + alias: "My Device", + userNumber: BigInt(1234), + }); }, loginPasskey: async () => { await new Promise((resolve) => setTimeout(resolve, 2000));