@@ -19,11 +19,16 @@ import { inferPasskeyAlias, loadUAParser } from "$lib/legacy/flows/register";
19
19
import { lastUsedIdentitiesStore } from "$lib/stores/last-used-identities.store" ;
20
20
import { throwCanisterError } from "$lib/utils/utils" ;
21
21
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" ;
22
26
23
27
export class MigrationFlow {
24
28
view = $state < "enterNumber" | "enterName" > ( "enterNumber" ) ;
25
29
identityNumber : UserNumber | undefined ;
26
30
authenticating = $state ( false ) ;
31
+ #webAuthFlows: { flows : WebAuthnFlow [ ] ; currentIndex : number } | undefined ;
27
32
28
33
constructor ( ) {
29
34
this . identityNumber = undefined ;
@@ -35,28 +40,78 @@ export class MigrationFlow {
35
40
this . authenticating = true ;
36
41
this . identityNumber = identityNumber ;
37
42
const devices = await this . #lookupAuthenticators( identityNumber ) ;
43
+
38
44
const webAuthnAuthenticators = devices
39
45
. filter ( ( { key_type } ) => ! ( "browser_storage_key" in key_type ) )
40
46
. map ( convertToValidCredentialData )
41
47
. 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
+
42
73
const passkeyIdentity = MultiWebAuthnIdentity . fromCredentials (
43
74
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 ,
56
77
) ;
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
+ }
60
115
} ;
61
116
62
117
createPasskey = async ( name : string ) : Promise < void > => {
0 commit comments