Skip to content

Commit 345e2cf

Browse files
authored
Use RP ID and auth flows when authenticating in migration flow (#3144)
1 parent 2fb654c commit 345e2cf

File tree

1 file changed

+70
-15
lines changed

1 file changed

+70
-15
lines changed

src/frontend/src/lib/flows/migrationFlow.svelte.ts

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ import { inferPasskeyAlias, loadUAParser } from "$lib/legacy/flows/register";
1919
import { lastUsedIdentitiesStore } from "$lib/stores/last-used-identities.store";
2020
import { throwCanisterError } from "$lib/utils/utils";
2121
import { toaster } from "$lib/components/utils/toaster";
22+
import { findWebAuthnFlows, WebAuthnFlow } from "$lib/utils/findWebAuthnFlows";
23+
import { supportsWebauthRoR } from "$lib/utils/userAgent";
24+
import { canisterConfig } from "$lib/globals";
25+
import { isWebAuthnCancelError } from "$lib/utils/webAuthnErrorUtils";
2226

2327
export class MigrationFlow {
2428
view = $state<"enterNumber" | "enterName">("enterNumber");
2529
identityNumber: UserNumber | undefined;
2630
authenticating = $state(false);
31+
#webAuthFlows: { flows: WebAuthnFlow[]; currentIndex: number } | undefined;
2732

2833
constructor() {
2934
this.identityNumber = undefined;
@@ -35,28 +40,78 @@ export class MigrationFlow {
3540
this.authenticating = true;
3641
this.identityNumber = identityNumber;
3742
const devices = await this.#lookupAuthenticators(identityNumber);
43+
3844
const webAuthnAuthenticators = devices
3945
.filter(({ key_type }) => !("browser_storage_key" in key_type))
4046
.map(convertToValidCredentialData)
4147
.filter(nonNullish);
48+
49+
if (isNullish(this.#webAuthFlows)) {
50+
const flows = findWebAuthnFlows({
51+
supportsRor: supportsWebauthRoR(window.navigator.userAgent),
52+
devices: webAuthnAuthenticators,
53+
currentOrigin: window.location.origin,
54+
// Empty array is the same as no related origins.
55+
relatedOrigins: canisterConfig.related_origins[0] ?? [],
56+
});
57+
this.#webAuthFlows = {
58+
flows,
59+
currentIndex: 0,
60+
};
61+
}
62+
63+
const flowsLength = this.#webAuthFlows?.flows.length ?? 0;
64+
// We reached the last flow. Start from the beginning.
65+
// This might happen if the user cancelled manually in the flow that would have been successful.
66+
if (this.#webAuthFlows?.currentIndex === flowsLength) {
67+
this.#webAuthFlows.currentIndex = 0;
68+
}
69+
const currentFlow = nonNullish(this.#webAuthFlows)
70+
? this.#webAuthFlows.flows[this.#webAuthFlows.currentIndex]
71+
: undefined;
72+
4273
const passkeyIdentity = MultiWebAuthnIdentity.fromCredentials(
4374
webAuthnAuthenticators,
44-
undefined,
45-
false,
46-
);
47-
const session = get(sessionStore);
48-
const delegation = await DelegationChain.create(
49-
passkeyIdentity,
50-
session.identity.getPublicKey(),
51-
new Date(Date.now() + 30 * 60 * 1000),
52-
);
53-
const identity = DelegationIdentity.fromDelegation(
54-
session.identity,
55-
delegation,
75+
currentFlow?.rpId,
76+
currentFlow?.useIframe ?? false,
5677
);
57-
authenticationStore.set({ identity, identityNumber });
58-
this.authenticating = false;
59-
this.view = "enterName";
78+
try {
79+
const session = get(sessionStore);
80+
const delegation = await DelegationChain.create(
81+
passkeyIdentity,
82+
session.identity.getPublicKey(),
83+
new Date(Date.now() + 30 * 60 * 1000),
84+
);
85+
const identity = DelegationIdentity.fromDelegation(
86+
session.identity,
87+
delegation,
88+
);
89+
authenticationStore.set({ identity, identityNumber });
90+
this.view = "enterName";
91+
} catch (e: unknown) {
92+
if (isWebAuthnCancelError(e)) {
93+
// We only want to show a special error if the user might have to choose different web auth flow.
94+
if (nonNullish(this.#webAuthFlows) && flowsLength > 1) {
95+
// Increase the index to try the next flow.
96+
this.#webAuthFlows = {
97+
flows: this.#webAuthFlows.flows,
98+
currentIndex: this.#webAuthFlows.currentIndex + 1,
99+
};
100+
toaster.info({
101+
title: "Please try again",
102+
description:
103+
"The wrong domain was set for the passkey and the browser couldn't find it.",
104+
});
105+
return;
106+
}
107+
}
108+
109+
throw new Error(
110+
"Failed to authenticate using passkey. Please try again and contact support if the issue persists.",
111+
);
112+
} finally {
113+
this.authenticating = false;
114+
}
60115
};
61116

62117
createPasskey = async (name: string): Promise<void> => {

0 commit comments

Comments
 (0)