Skip to content

Commit db2e539

Browse files
authored
Move auth flow out of components (#3130)
* Move auth flow out of components * Fix broken imports * Fix broken imports * Fix broken imports * Fix broken imports * Fix broken imports
1 parent c7b52de commit db2e539

File tree

161 files changed

+930
-1195
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+930
-1195
lines changed

src/frontend/src/hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Reroute } from "@sveltejs/kit";
2-
import { WEBAUTHN_IFRAME_PATH } from "$lib/flows/iframeWebAuthn";
2+
import { WEBAUTHN_IFRAME_PATH } from "$lib/legacy/flows/iframeWebAuthn";
33
import { getAddDeviceAnchor } from "$lib/utils/addDeviceLink";
44
import { nonNullish } from "@dfinity/utils";
55
import { DISCOVERABLE_PASSKEY_FLOW } from "$lib/state/featureFlags";

src/frontend/src/lib/components/ui/AuthorizeHeader.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { isNullish, nonNullish } from "@dfinity/utils";
33
import Badge from "$lib/components/ui/Badge.svelte";
44
import Ellipsis from "$lib/components/utils/Ellipsis.svelte";
5-
import { getDapps } from "$lib/flows/dappsExplorer/dapps.js";
5+
import { getDapps } from "$lib/legacy/flows/dappsExplorer/dapps.js";
66
import type { HTMLAttributes } from "svelte/elements";
77
import { GlobeIcon } from "@lucide/svelte";
88

src/frontend/src/lib/components/views/UseAnotherIdentity.svelte

Lines changed: 21 additions & 319 deletions
Original file line numberDiff line numberDiff line change
@@ -4,353 +4,55 @@
44
import SolveCaptcha from "$lib/components/views/SolveCaptcha.svelte";
55
import SetupOrUseExistingPasskey from "$lib/components/views/SetupOrUseExistingPasskey.svelte";
66
import CreatePasskey from "$lib/components/views/CreatePasskey.svelte";
7-
import {
8-
AuthenticationV2Events,
9-
authenticationV2Funnel,
10-
} from "$lib/utils/analytics/authenticationV2Funnel";
11-
import {
12-
authenticateWithJWT,
13-
authenticateWithPasskey,
14-
authenticateWithSession,
15-
} from "$lib/utils/authentication";
16-
import { canisterConfig, canisterId } from "$lib/globals";
17-
import {
18-
authenticatedStore,
19-
authenticationStore,
20-
} from "$lib/stores/authentication.store";
21-
import { lastUsedIdentitiesStore } from "$lib/stores/last-used-identities.store";
227
import { handleError } from "$lib/components/utils/error";
23-
import { DiscoverablePasskeyIdentity } from "$lib/utils/discoverablePasskeyIdentity";
24-
import { inferPasskeyAlias, loadUAParser } from "$lib/flows/register";
25-
import { passkeyAuthnMethodData } from "$lib/utils/authnMethodData";
26-
import { isCanisterError, throwCanisterError } from "$lib/utils/utils";
278
import { toaster } from "$lib/components/utils/toaster";
28-
import type {
29-
CheckCaptchaError,
30-
IdRegFinishError,
31-
IdRegStartError,
32-
OpenIdDelegationError,
33-
} from "$lib/generated/internet_identity_types";
34-
import { createGoogleRequestConfig, requestJWT } from "$lib/utils/openID";
35-
import { sessionStore } from "$lib/stores/session.store";
369
import SystemOverlayBackdrop from "$lib/components/utils/SystemOverlayBackdrop.svelte";
37-
import { onMount } from "svelte";
38-
import { features } from "$lib/legacy/features";
39-
import { DiscoverableDummyIdentity } from "$lib/utils/discoverableDummyIdentity";
10+
import { AuthFlow } from "$lib/flows/authFlow.svelte";
4011
4112
interface Props {
42-
onCancel: () => void;
4313
onSuccess: (identityNumber: bigint) => void;
14+
onCancel: () => void;
4415
}
4516
4617
const { onCancel, onSuccess }: Props = $props();
4718
48-
let view = $state<
49-
"chooseMethod" | "setupOrUseExistingPasskey" | "setupNewPasskey"
50-
>("chooseMethod");
51-
let captcha = $state<{
52-
image: string;
53-
attempt: number;
54-
solve: (solution: string) => void;
55-
}>();
56-
let systemOverlay = $state(false);
57-
58-
const setupOrUseExistingPasskey = async () => {
59-
authenticationV2Funnel.trigger(
60-
AuthenticationV2Events.ContinueWithPasskeyScreen,
61-
);
62-
view = "setupOrUseExistingPasskey";
63-
};
64-
65-
const continueWithExistingPasskey = async () => {
66-
try {
67-
authenticationV2Funnel.trigger(AuthenticationV2Events.UseExistingPasskey);
68-
const { identity, identityNumber, credentialId } =
69-
await authenticateWithPasskey({
70-
canisterId,
71-
session: $sessionStore,
72-
});
73-
authenticationStore.set({ identity, identityNumber });
74-
const info =
75-
await $authenticatedStore.actor.get_anchor_info(identityNumber);
76-
lastUsedIdentitiesStore.addLastUsedIdentity({
77-
identityNumber,
78-
name: info.name[0],
79-
authMethod: { passkey: { credentialId } },
80-
});
81-
onSuccess(identityNumber);
82-
} catch (error) {
83-
handleError(error);
84-
onCancel();
85-
}
86-
};
87-
88-
const setupNewPasskey = () => {
89-
authenticationV2Funnel.trigger(AuthenticationV2Events.EnterNameScreen);
90-
view = "setupNewPasskey";
91-
};
92-
93-
const createPasskey = async (name: string) => {
94-
authenticationV2Funnel.trigger(
95-
AuthenticationV2Events.StartWebauthnCreation,
96-
);
97-
try {
98-
const passkeyIdentity =
99-
features.DUMMY_AUTH || nonNullish(canisterConfig.dummy_auth[0]?.[0])
100-
? await DiscoverableDummyIdentity.createNew(name)
101-
: await DiscoverablePasskeyIdentity.createNew(name);
102-
await startRegistration();
103-
await registerWithPasskey(passkeyIdentity);
104-
} catch (error) {
105-
handleError(error);
106-
onCancel();
107-
}
108-
};
109-
110-
const registerWithPasskey = async (
111-
passkeyIdentity: DiscoverablePasskeyIdentity,
112-
attempts = 0,
113-
) => {
114-
authenticationV2Funnel.trigger(AuthenticationV2Events.RegisterWithPasskey);
115-
const uaParser = loadUAParser();
116-
const alias = await inferPasskeyAlias({
117-
authenticatorType: passkeyIdentity.getAuthenticatorAttachment(),
118-
userAgent: navigator.userAgent,
119-
uaParser,
120-
aaguid: passkeyIdentity.getAaguid(),
121-
});
122-
const authnMethod = passkeyAuthnMethodData({
123-
alias,
124-
pubKey: passkeyIdentity.getPublicKey().toDer(),
125-
credentialId: passkeyIdentity.getCredentialId()!,
126-
authenticatorAttachment: passkeyIdentity.getAuthenticatorAttachment(),
127-
origin: window.location.origin,
128-
});
129-
const name = passkeyIdentity.getName();
130-
try {
131-
const { identity_number: identityNumber } = await $sessionStore.actor
132-
.identity_registration_finish({
133-
name: nonNullish(name) ? [name] : [],
134-
authn_method: authnMethod,
135-
})
136-
.then(throwCanisterError);
137-
authenticationV2Funnel.trigger(
138-
AuthenticationV2Events.SuccessfulPasskeyRegistration,
139-
);
140-
const credentialId = new Uint8Array(passkeyIdentity.getCredentialId()!);
141-
const identity = await authenticateWithSession({
142-
session: $sessionStore,
143-
});
144-
authenticationStore.set({ identity, identityNumber });
145-
lastUsedIdentitiesStore.addLastUsedIdentity({
146-
identityNumber,
147-
name: passkeyIdentity.getName(),
148-
authMethod: { passkey: { credentialId } },
149-
});
19+
const authFlow = new AuthFlow({
20+
onSignIn: onSuccess,
21+
onSignUp: (identityNumber) => {
15022
toaster.success({
15123
title: "You're all set. Your identity has been created.",
15224
duration: 4000,
15325
closable: false,
15426
});
15527
onSuccess(identityNumber);
156-
} catch (error) {
157-
if (isCanisterError<IdRegFinishError>(error)) {
158-
switch (error.type) {
159-
case "UnexpectedCall":
160-
const nextStep = error.value(error.type).next_step;
161-
if ("CheckCaptcha" in nextStep) {
162-
if (attempts < 3) {
163-
await solveCaptcha(
164-
`data:image/png;base64,${nextStep.CheckCaptcha.captcha_png_base64}`,
165-
);
166-
return registerWithPasskey(passkeyIdentity, attempts + 1);
167-
}
168-
}
169-
break;
170-
case "NoRegistrationFlow":
171-
if (attempts < 3) {
172-
// Apparently the flow has been cleaned up, try again.
173-
await startRegistration();
174-
return await registerWithPasskey(passkeyIdentity, attempts + 1);
175-
}
176-
break;
177-
}
178-
}
28+
},
29+
onError: (error) => {
17930
handleError(error);
18031
onCancel();
181-
}
182-
};
183-
184-
const continueWithGoogle = async () => {
185-
let jwt: string | undefined;
186-
try {
187-
authenticationV2Funnel.trigger(AuthenticationV2Events.ContinueWithGoogle);
188-
const clientId = canisterConfig.openid_google?.[0]?.[0]?.client_id!;
189-
const requestConfig = createGoogleRequestConfig(clientId);
190-
systemOverlay = true;
191-
jwt = await requestJWT(requestConfig, {
192-
nonce: $sessionStore.nonce,
193-
mediation: "required",
194-
});
195-
systemOverlay = false;
196-
const { identity, identityNumber, iss, sub } = await authenticateWithJWT({
197-
canisterId,
198-
session: $sessionStore,
199-
jwt,
200-
});
201-
// If the previous call succeeds, it means the Google user already exists in II.
202-
// Therefore, they are logging in.
203-
// If the call fails, it means the Google user does not exist in II.
204-
// In that case, we register them.
205-
authenticationV2Funnel.trigger(AuthenticationV2Events.LoginWithGoogle);
206-
authenticationStore.set({ identity, identityNumber });
207-
const info =
208-
await $authenticatedStore.actor.get_anchor_info(identityNumber);
209-
lastUsedIdentitiesStore.addLastUsedIdentity({
210-
identityNumber,
211-
name: info.name[0],
212-
authMethod: { openid: { iss, sub } },
213-
});
214-
onSuccess(identityNumber);
215-
} catch (error) {
216-
systemOverlay = false;
217-
if (
218-
isCanisterError<OpenIdDelegationError>(error) &&
219-
error.type === "NoSuchAnchor" &&
220-
nonNullish(jwt)
221-
) {
222-
authenticationV2Funnel.trigger(
223-
AuthenticationV2Events.RegisterWithGoogle,
224-
);
225-
await startRegistration();
226-
return registerWithGoogle(jwt);
227-
}
228-
handleError(error);
229-
onCancel();
230-
}
231-
};
232-
233-
const startRegistration = async (): Promise<void> => {
234-
try {
235-
const { next_step } = await $sessionStore.actor
236-
.identity_registration_start()
237-
.then(throwCanisterError);
238-
if ("CheckCaptcha" in next_step) {
239-
await solveCaptcha(
240-
`data:image/png;base64,${next_step.CheckCaptcha.captcha_png_base64}`,
241-
);
242-
}
243-
} catch (error) {
244-
if (
245-
isCanisterError<IdRegStartError>(error) &&
246-
error.type === "AlreadyInProgress"
247-
) {
248-
// Ignore since it means we can continue with an existing registration
249-
return;
250-
}
251-
handleError(error);
252-
onCancel();
253-
}
254-
};
255-
256-
const solveCaptcha = async (image: string, attempt = 0): Promise<void> =>
257-
new Promise((resolve) => {
258-
captcha = {
259-
image,
260-
attempt,
261-
solve: async (solution) => {
262-
try {
263-
await $sessionStore.actor
264-
.check_captcha({ solution })
265-
.then(throwCanisterError);
266-
resolve();
267-
} catch (error) {
268-
if (
269-
isCanisterError<CheckCaptchaError>(error) &&
270-
error.type === "WrongSolution"
271-
) {
272-
const nextImage = `data:image/png;base64,${error.value(error.type).new_captcha_png_base64}`;
273-
await solveCaptcha(nextImage, attempt + 1);
274-
resolve();
275-
return;
276-
}
277-
handleError(error);
278-
onCancel();
279-
}
280-
},
281-
};
282-
});
283-
284-
const registerWithGoogle = async (jwt: string) => {
285-
try {
286-
await $sessionStore.actor
287-
.openid_identity_registration_finish({
288-
jwt,
289-
salt: $sessionStore.salt,
290-
})
291-
.then(throwCanisterError);
292-
const { identity, identityNumber, iss, sub } = await authenticateWithJWT({
293-
canisterId,
294-
session: $sessionStore,
295-
jwt,
296-
});
297-
authenticationV2Funnel.trigger(
298-
AuthenticationV2Events.SuccessfulGoogleRegistration,
299-
);
300-
authenticationStore.set({ identity, identityNumber });
301-
const info =
302-
await $authenticatedStore.actor.get_anchor_info(identityNumber);
303-
lastUsedIdentitiesStore.addLastUsedIdentity({
304-
identityNumber,
305-
name: info.name[0],
306-
authMethod: { openid: { iss, sub } },
307-
});
308-
toaster.success({
309-
title: "You're all set. Your identity has been created.",
310-
duration: 4000,
311-
closable: false,
312-
});
313-
onSuccess(identityNumber);
314-
} catch (error) {
315-
if (
316-
isCanisterError<IdRegFinishError>(error) &&
317-
error.type === "UnexpectedCall"
318-
) {
319-
const nextStep = error.value(error.type).next_step;
320-
if ("CheckCaptcha" in nextStep) {
321-
await solveCaptcha(
322-
`data:image/png;base64,${nextStep.CheckCaptcha.captcha_png_base64}`,
323-
);
324-
return registerWithGoogle(jwt);
325-
}
326-
}
327-
handleError(error);
328-
onCancel();
329-
}
330-
};
331-
332-
onMount(() => {
333-
authenticationV2Funnel.trigger(AuthenticationV2Events.SelectMethodScreen);
32+
},
33433
});
33534
</script>
33635

337-
{#if nonNullish(captcha)}
338-
<SolveCaptcha {...captcha} />
339-
{:else if view === "chooseMethod"}
36+
{#if nonNullish(authFlow.captcha)}
37+
<SolveCaptcha {...authFlow.captcha} />
38+
{:else if authFlow.view === "chooseMethod"}
34039
<h1 class="text-text-primary mb-2 text-2xl font-medium">
34140
Use another identity
34241
</h1>
34342
<p class="text-text-secondary mb-6 self-start text-sm">Choose method</p>
344-
<PickAuthenticationMethod {setupOrUseExistingPasskey} {continueWithGoogle} />
345-
{:else if view === "setupOrUseExistingPasskey"}
43+
<PickAuthenticationMethod
44+
setupOrUseExistingPasskey={authFlow.setupOrUseExistingPasskey}
45+
continueWithGoogle={authFlow.continueWithGoogle}
46+
/>
47+
{:else if authFlow.view === "setupOrUseExistingPasskey"}
34648
<SetupOrUseExistingPasskey
347-
setupNew={setupNewPasskey}
348-
useExisting={continueWithExistingPasskey}
49+
setupNew={authFlow.setupNewPasskey}
50+
useExisting={authFlow.continueWithExistingPasskey}
34951
/>
350-
{:else if view === "setupNewPasskey"}
351-
<CreatePasskey create={createPasskey} />
52+
{:else if authFlow.view === "setupNewPasskey"}
53+
<CreatePasskey create={authFlow.createPasskey} />
35254
{/if}
35355

354-
{#if systemOverlay}
56+
{#if authFlow.systemOverlay}
35557
<SystemOverlayBackdrop />
35658
{/if}

0 commit comments

Comments
 (0)