Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/frontend/src/components/authenticateBox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { TemplateResult, html, render } from "lit-html";
import { infoToastTemplate } from "../infoToast";
import infoToastCopy from "../infoToast/copy.json";
import authnTemplatesCopy from "./authnTemplatesCopy.json";
import { LoginEvents, loginFunnel } from "$src/utils/analytics/loginFunnel";
/** Template used for rendering specific authentication screens. See `authnScreens` below
* for meaning of "firstTime", "useExisting" and "pick". */
export type AuthnTemplates = {
Expand Down Expand Up @@ -358,6 +359,7 @@ export const authenticateBoxFlow = async <I>({
> => {
const result = await pages.useExisting();
if (result.tag === "submit") {
loginFunnel.trigger(LoginEvents.TriggerUseExisting);
return doLogin({ userNumber: result.userNumber });
}

Expand Down Expand Up @@ -402,10 +404,12 @@ export const authenticateBoxFlow = async <I>({
});

if (result.tag === "pick") {
loginFunnel.trigger(LoginEvents.TriggerListItem);
return doLogin({ userNumber: result.userNumber });
}

result satisfies { tag: "more_options" };
loginFunnel.trigger(LoginEvents.GoUseExisting);
return await doPrompt();
} else {
const result = await pages.firstTime();
Expand Down Expand Up @@ -843,6 +847,10 @@ const useIdentityFlow = async <I>({

const doLoginPasskey = async () => {
const result = await withLoader(() => loginPasskey(userNumber));
// We need to trigger the success here because later we don't know whether it was a registration or login.
if (result.kind === "loginSuccess") {
loginFunnel.trigger(LoginEvents.Success);
}
return { newAnchor: false, authnMethod: "passkey", ...result } as const;
};

Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/flows/authorize/postMessageInterface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Types and functions related to the window post message interface used by
// applications that want to authenticate the user using Internet Identity
import { analytics } from "$src/utils/analytics/analytics";
import { loginFunnel } from "$src/utils/analytics/loginFunnel";
import { type SignedDelegation as FrontendSignedDelegation } from "@dfinity/identity";
import { Principal } from "@dfinity/principal";
import { z } from "zod";
Expand Down Expand Up @@ -130,6 +131,8 @@ export async function authenticationProtocol({
analytics.event("authorize-client-request-valid", {
origin: requestOrigin,
});
// TODO: Add origin to login funnel
loginFunnel.init();

Comment thread
lmuntaner marked this conversation as resolved.
const authContext = {
authRequest: requestResult.request,
Expand Down
25 changes: 25 additions & 0 deletions src/frontend/src/utils/analytics/loginFunnel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Funnel } from "./Funnel";

/**
* Login flow events:
*
* Square brackets [] indicate optional events.
*
* login-start (INIT) - Triggered in Landing Page or List of identities
* login-trigger-list-item - Triggered when user clicks on a number
* login-webauthn-start - Triggered when the webauthn is triggered
* login-success - Triggered after successful webauthn interaction
* go-use-existing - Triggered on visiting the Use Existing page
* login-trigger-use-existing - Triggered when user clicks "Continue" in the Use Existing
* login-webauthn-start - Triggered when the webauthn is triggered
* login-success - Triggered after successful webauthn interaction
*/
export enum LoginEvents {
GoUseExisting = "go-use-existing",
TriggerListItem = "login-trigger-list-item",
TriggerUseExisting = "login-trigger-use-existing",
WebauthnStart = "login-webauthn-start",
Success = "login-success",
}

export const loginFunnel = new Funnel<typeof LoginEvents>("login");
6 changes: 2 additions & 4 deletions src/frontend/src/utils/iiConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import { MultiWebAuthnIdentity } from "./multiWebAuthnIdentity";
import { isRecoveryDevice, RecoveryDevice } from "./recoveryDevice";
import { supportsWebauthRoR } from "./userAgent";
import { isWebAuthnCancel } from "./webAuthnErrorUtils";
import { LoginEvents, loginFunnel } from "./analytics/loginFunnel";

/*
* A (dummy) identity that always uses the same keypair. The secret key is
Expand Down Expand Up @@ -463,12 +464,10 @@ export class Connection {
| UnknownUser
| ApiError
> => {
analytics.event("login-passkey-start");
let devices: Omit<DeviceData, "alias">[];
try {
devices = await this.lookupAuthenticators(userNumber);
} catch (e: unknown) {
analytics.event("login-passkey-error-lookup");
const errObj =
e instanceof Error
? e
Expand All @@ -477,7 +476,6 @@ export class Connection {
}

if (devices.length === 0) {
analytics.event("login-passkey-error-unknown");
return { kind: "unknownUser", userNumber };
}

Expand All @@ -488,7 +486,6 @@ export class Connection {
// If we reach this point, it's because no PIN identity was found.
// Therefore, it's because it was created in another domain.
if (webAuthnAuthenticators.length === 0) {
analytics.event("login-passkey-error-pin-other-domain");
return { kind: "pinUserOtherDomain" };
}

Expand All @@ -500,6 +497,7 @@ export class Connection {
);
}

loginFunnel.trigger(LoginEvents.WebauthnStart);
return this.fromWebauthnCredentials(
userNumber,
webAuthnAuthenticators
Expand Down