Skip to content

Commit 187fd62

Browse files
authored
Refactor single authorize page into multiple pages and stores. (#3062)
* WIP * WIP * Refactor single authorize page into multiple pages and stores. * Refactor single authorize page into multiple pages and stores. * Refactor single authorize page into multiple pages and stores. * Fix create account * Fix create account * Fix account creation focus management. * Autofocus continue button. * Fix focus styles * Fixes. * Fixes. * Fixes. * Fixes. * Fixes.
1 parent d3979a4 commit 187fd62

Some content is hidden

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

44 files changed

+1313
-1155
lines changed

src/frontend/src/hooks.client.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import type { ServerInit } from "@sveltejs/kit";
1+
import type { ClientInit } from "@sveltejs/kit";
22
import featureFlags from "$lib/state/featureFlags";
3+
import { authenticationStore } from "$lib/stores/authentication.store";
4+
import { sessionStore } from "$lib/stores/session.store";
5+
import { initGlobals, canisterId, agentOptions } from "$lib/globals";
36

47
const FEATURE_FLAG_PREFIX = "feature_flag_";
58

6-
export const init: ServerInit = () => {
9+
const overrideFeatureFlags = () => {
710
// Override feature flags based on search params before any other code
811
// including other hooks runs that might depend on these feature flags.
912
//
@@ -36,3 +39,12 @@ export const init: ServerInit = () => {
3639
// not using the method supplied by SvelteKit, this can be safely ignored.
3740
window.history.replaceState(undefined, "", url);
3841
};
42+
43+
export const init: ClientInit = async () => {
44+
overrideFeatureFlags();
45+
await initGlobals();
46+
await Promise.all([
47+
sessionStore.init({ canisterId, agentOptions }),
48+
authenticationStore.init({ canisterId, agentOptions }),
49+
]);
50+
};

src/frontend/src/hooks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ export const reroute: Reroute = ({ url }) => {
1616
return "/vc-flow/index";
1717
}
1818
if (url.hash === "#authorize") {
19-
return get(DISCOVERABLE_PASSKEY_FLOW) ? "/new-authorize" : "/authorize";
19+
return get(DISCOVERABLE_PASSKEY_FLOW) ? "/authorize" : "/legacy/authorize";
2020
}
2121
};

src/frontend/src/lib/components/UI/Dialog.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
? "fixed top-auto bottom-0 mx-auto"
6060
: "m-auto max-[460px]:m-0 max-[460px]:min-h-full",
6161
]}
62-
transition:transitionFn|global
62+
transition:transitionFn
6363
onoutrostart={fadeOutBackDrop}
6464
{...props}
6565
>

src/frontend/src/lib/globals.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Principal } from "@dfinity/principal";
2+
import {
3+
type _SERVICE,
4+
InternetIdentityInit,
5+
} from "$lib/generated/internet_identity_types";
6+
import { readCanisterConfig, readCanisterId } from "$lib/utils/init";
7+
import {
8+
Actor,
9+
ActorSubclass,
10+
HttpAgent,
11+
HttpAgentOptions,
12+
} from "@dfinity/agent";
13+
import { inferHost } from "$lib/utils/iiConnection";
14+
import { idlFactory as internet_identity_idl } from "$lib/generated/internet_identity_idl";
15+
import { features } from "$lib/legacy/features";
16+
17+
export let canisterId: Principal;
18+
export let canisterConfig: InternetIdentityInit;
19+
export let agentOptions: HttpAgentOptions;
20+
export let anonymousAgent: HttpAgent;
21+
export let anonymousActor: ActorSubclass<_SERVICE>;
22+
23+
export const initGlobals = async () => {
24+
canisterId = Principal.fromText(readCanisterId());
25+
canisterConfig = readCanisterConfig();
26+
agentOptions = {
27+
host: inferHost(),
28+
shouldFetchRootKey:
29+
features.FETCH_ROOT_KEY || (canisterConfig.fetch_root_key[0] ?? false),
30+
};
31+
anonymousAgent = await HttpAgent.create(agentOptions);
32+
anonymousActor = Actor.createActor<_SERVICE>(internet_identity_idl, {
33+
agent: anonymousAgent,
34+
canisterId,
35+
});
36+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { type Readable, derived, writable } from "svelte/store";
2+
import { DelegationIdentity } from "@dfinity/identity";
3+
import { isNullish, nonNullish } from "@dfinity/utils";
4+
import {
5+
Actor,
6+
ActorSubclass,
7+
HttpAgent,
8+
HttpAgentOptions,
9+
} from "@dfinity/agent";
10+
import { Principal } from "@dfinity/principal";
11+
import type { _SERVICE } from "$lib/generated/internet_identity_types";
12+
import { idlFactory as internet_identity_idl } from "$lib/generated/internet_identity_idl";
13+
14+
export interface Authenticated {
15+
identityNumber: bigint;
16+
identity: DelegationIdentity;
17+
agent: HttpAgent;
18+
actor: ActorSubclass<_SERVICE>;
19+
}
20+
21+
type AuthenticationStore = Readable<Authenticated | undefined> & {
22+
init: (params: {
23+
canisterId: Principal;
24+
agentOptions: HttpAgentOptions;
25+
}) => Promise<void>;
26+
set: (value: Omit<Authenticated, "agent" | "actor">) => void;
27+
reset: () => void;
28+
};
29+
30+
const internalStore = writable<{
31+
authenticated?: Omit<Authenticated, "agent" | "actor">;
32+
initialized?: Pick<Authenticated, "agent" | "actor">;
33+
}>();
34+
35+
export const authenticationStore: AuthenticationStore = {
36+
init: async ({ canisterId, agentOptions }) => {
37+
const agent = await HttpAgent.create(agentOptions);
38+
const actor = Actor.createActor<_SERVICE>(internet_identity_idl, {
39+
agent,
40+
canisterId,
41+
});
42+
internalStore.set({ initialized: { agent, actor } });
43+
},
44+
subscribe: derived(internalStore, ({ authenticated, initialized }) => {
45+
if (isNullish(initialized)) {
46+
throw new Error("Not initialized");
47+
}
48+
if (isNullish(authenticated)) {
49+
return undefined;
50+
}
51+
return { ...authenticated, ...initialized };
52+
}).subscribe,
53+
set: (authenticated) =>
54+
internalStore.update(({ initialized }) => {
55+
if (isNullish(initialized)) {
56+
throw new Error("Not initialized");
57+
}
58+
initialized.agent.replaceIdentity(authenticated.identity);
59+
return { authenticated, initialized };
60+
}),
61+
reset: () =>
62+
internalStore.update(({ initialized }) => {
63+
if (isNullish(initialized)) {
64+
throw new Error("Not initialized");
65+
}
66+
initialized.agent.invalidateIdentity();
67+
return { authenticated: undefined, initialized };
68+
}),
69+
};
70+
71+
export const authenticatedStore: Readable<Authenticated> = derived(
72+
authenticationStore,
73+
(authenticated) => {
74+
if (isNullish(authenticated)) {
75+
throw new Error("Not authenticated");
76+
}
77+
return authenticated;
78+
},
79+
);
80+
81+
export const isAuthenticatedStore: Readable<boolean> = derived(
82+
authenticationStore,
83+
(authenticated) => nonNullish(authenticated),
84+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { type Readable, derived, writable, get } from "svelte/store";
2+
import { isNullish } from "@dfinity/utils";
3+
import type { InternetIdentityInit } from "$lib/generated/internet_identity_types";
4+
import { Principal } from "@dfinity/principal";
5+
import {
6+
authenticationProtocol,
7+
AuthRequest,
8+
} from "$lib/flows/authorize/postMessageInterface";
9+
import { authenticatedStore } from "$lib/stores/authentication.store";
10+
import { Connection } from "$lib/utils/iiConnection";
11+
import { fetchDelegation } from "$lib/flows/authorize/fetchDelegation";
12+
13+
export type AuthorizationContext = {
14+
authRequest: AuthRequest;
15+
requestOrigin: string;
16+
};
17+
18+
export type AuthorizationStatus =
19+
| "init"
20+
| "orphan"
21+
| "closed"
22+
| "waiting"
23+
| "validating"
24+
| "invalid"
25+
| "authenticating"
26+
| "success"
27+
| "failure";
28+
29+
type AuthorizationStore = Readable<{
30+
context?: AuthorizationContext;
31+
status: AuthorizationStatus;
32+
}> & {
33+
init: (params: {
34+
canisterId: Principal;
35+
canisterConfig: InternetIdentityInit;
36+
}) => Promise<void>;
37+
authorize: (accountNumber: bigint | undefined) => Promise<void>;
38+
};
39+
40+
const internalStore = writable<{
41+
context?: AuthorizationContext;
42+
status: AuthorizationStatus;
43+
}>({ status: "init" });
44+
45+
let authorize: (accountNumber: bigint | undefined) => Promise<void>;
46+
47+
export const authorizationStore: AuthorizationStore = {
48+
init: async ({ canisterId, canisterConfig }) => {
49+
const status = await authenticationProtocol({
50+
authenticate: (context) => {
51+
console.log("authenticate", context);
52+
internalStore.set({ context, status: "authenticating" });
53+
return new Promise((resolve) => {
54+
authorize = async (_accountNumber) => {
55+
// TODO: use prepare/get account delegation instead of iiConnection
56+
const { identityNumber, identity } = get(authenticatedStore);
57+
const { connection } = await new Connection(
58+
canisterId.toText(),
59+
canisterConfig,
60+
).fromDelegationIdentity(identityNumber, identity);
61+
const derivationOrigin =
62+
context.authRequest.derivationOrigin ?? context.requestOrigin;
63+
const result = await fetchDelegation({
64+
connection,
65+
derivationOrigin,
66+
publicKey: context.authRequest.sessionPublicKey,
67+
maxTimeToLive: context.authRequest.maxTimeToLive,
68+
});
69+
if ("error" in result) {
70+
resolve({ kind: "failure", text: "Couldn't fetch delegation" });
71+
return;
72+
}
73+
const [userKey, parsed_signed_delegation] = result;
74+
resolve({
75+
kind: "success",
76+
delegations: [parsed_signed_delegation],
77+
userPublicKey: new Uint8Array(userKey),
78+
// This is a authnMethod forwarded to the app that requested authorization.
79+
// We don't want to leak which authnMethod was used.
80+
authnMethod: "passkey",
81+
});
82+
};
83+
});
84+
},
85+
onProgress: (status) =>
86+
internalStore.update((value) => ({ ...value, status })),
87+
});
88+
internalStore.update((value) => ({ ...value, status }));
89+
},
90+
subscribe: (...args) => internalStore.subscribe(...args),
91+
authorize: (accountNumber) => {
92+
if (isNullish(authorize)) {
93+
throw new Error("Not ready yet for authorization");
94+
}
95+
return authorize(accountNumber);
96+
},
97+
};
98+
99+
export const authorizationContextStore: Readable<AuthorizationContext> =
100+
derived(authorizationStore, ({ context }) => {
101+
if (isNullish(context)) {
102+
throw new Error("Authorization context is not available yet");
103+
}
104+
return context;
105+
});
106+
107+
export const authorizationStatusStore: Readable<AuthorizationStatus> = derived(
108+
internalStore,
109+
({ status }) => status,
110+
);

0 commit comments

Comments
 (0)