Skip to content

Commit b0ec63e

Browse files
sosweethamcoodos
andauthored
Fix/eid onboarding (#415)
* fix: onboarding * fix: key service * fix: make scan page less cucked * fix: signer event listener style * fix: format && check * fix: remove hardcoded platform url * chore: fix format * fix: remove unnecessary platform url * fix: format and check --------- Co-authored-by: Merul Dhiman <[email protected]>
1 parent 5f41b17 commit b0ec63e

File tree

14 files changed

+2339
-1876
lines changed

14 files changed

+2339
-1876
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { KeyManagerFactory } from "$lib/crypto";
2+
import type { KeyManager } from "$lib/crypto";
3+
import type { Store } from "@tauri-apps/plugin-store";
4+
5+
export type KeyServiceContext =
6+
| "onboarding"
7+
| "signing"
8+
| "verification"
9+
| "pre-verification";
10+
11+
type PersistedContext = {
12+
keyId: string;
13+
context: KeyServiceContext;
14+
managerType: "hardware" | "software";
15+
lastUsed: string;
16+
};
17+
18+
const CONTEXTS_KEY = "keyService.contexts";
19+
const READY_KEY = "keyService.ready";
20+
21+
export class KeyService {
22+
#store: Store;
23+
#managerCache = new Map<string, KeyManager>();
24+
#contexts = new Map<string, PersistedContext>();
25+
#ready = false;
26+
27+
constructor(store: Store) {
28+
this.#store = store;
29+
}
30+
31+
async initialize(): Promise<void> {
32+
const storedContexts =
33+
await this.#store.get<Record<string, PersistedContext>>(
34+
CONTEXTS_KEY,
35+
);
36+
if (storedContexts) {
37+
for (const [key, value] of Object.entries(storedContexts)) {
38+
this.#contexts.set(key, value);
39+
}
40+
}
41+
this.#ready = (await this.#store.get<boolean>(READY_KEY)) ?? false;
42+
}
43+
44+
get isReady(): boolean {
45+
return this.#ready;
46+
}
47+
48+
async setReady(value: boolean): Promise<void> {
49+
this.#ready = value;
50+
await this.#store.set(READY_KEY, value);
51+
}
52+
53+
async reset(): Promise<void> {
54+
this.#managerCache.clear();
55+
this.#contexts.clear();
56+
await this.#store.delete(CONTEXTS_KEY);
57+
await this.#store.delete(READY_KEY);
58+
this.#ready = false;
59+
}
60+
61+
async getManager(
62+
keyId: string,
63+
context: KeyServiceContext,
64+
): Promise<KeyManager> {
65+
const cacheKey = this.#getCacheKey(keyId, context);
66+
if (this.#managerCache.has(cacheKey)) {
67+
const cachedManager = this.#managerCache.get(cacheKey);
68+
if (cachedManager) {
69+
await this.#touchContext(cacheKey, cachedManager);
70+
return cachedManager;
71+
}
72+
this.#managerCache.delete(cacheKey);
73+
}
74+
75+
const manager = await KeyManagerFactory.getKeyManagerForContext(
76+
keyId,
77+
context,
78+
);
79+
this.#managerCache.set(cacheKey, manager);
80+
await this.#persistContext(cacheKey, manager, keyId, context);
81+
return manager;
82+
}
83+
84+
async ensureKey(
85+
keyId: string,
86+
context: KeyServiceContext,
87+
): Promise<{ manager: KeyManager; created: boolean }> {
88+
const manager = await this.getManager(keyId, context);
89+
const exists = await manager.exists(keyId);
90+
let created = false;
91+
if (!exists) {
92+
await manager.generate(keyId);
93+
await this.#touchContext(
94+
this.#getCacheKey(keyId, context),
95+
manager,
96+
);
97+
created = true;
98+
}
99+
return { manager, created };
100+
}
101+
102+
async getPublicKey(
103+
keyId: string,
104+
context: KeyServiceContext,
105+
): Promise<string | undefined> {
106+
const manager = await this.getManager(keyId, context);
107+
const publicKey = await manager.getPublicKey(keyId);
108+
await this.#touchContext(this.#getCacheKey(keyId, context), manager);
109+
return publicKey;
110+
}
111+
112+
async signPayload(
113+
keyId: string,
114+
context: KeyServiceContext,
115+
payload: string,
116+
): Promise<string> {
117+
const manager = await this.getManager(keyId, context);
118+
const signature = await manager.signPayload(keyId, payload);
119+
await this.#touchContext(this.#getCacheKey(keyId, context), manager);
120+
return signature;
121+
}
122+
123+
async verifySignature(
124+
keyId: string,
125+
context: KeyServiceContext,
126+
payload: string,
127+
signature: string,
128+
): Promise<boolean> {
129+
const manager = await this.getManager(keyId, context);
130+
const result = await manager.verifySignature(keyId, payload, signature);
131+
await this.#touchContext(this.#getCacheKey(keyId, context), manager);
132+
return result;
133+
}
134+
135+
async isHardwareAvailable(): Promise<boolean> {
136+
return KeyManagerFactory.isHardwareAvailable();
137+
}
138+
139+
#getCacheKey(keyId: string, context: KeyServiceContext): string {
140+
return `${context}:${keyId}`;
141+
}
142+
143+
async #persistContext(
144+
cacheKey: string,
145+
manager: KeyManager,
146+
keyId: string,
147+
context: KeyServiceContext,
148+
): Promise<void> {
149+
const entry: PersistedContext = {
150+
keyId,
151+
context,
152+
managerType: manager.getType(),
153+
lastUsed: new Date().toISOString(),
154+
};
155+
this.#contexts.set(cacheKey, entry);
156+
await this.#store.set(CONTEXTS_KEY, Object.fromEntries(this.#contexts));
157+
}
158+
159+
async #touchContext(cacheKey: string, manager?: KeyManager): Promise<void> {
160+
const current = this.#contexts.get(cacheKey);
161+
if (!current && manager) {
162+
const { keyId, context } = this.#parseCacheKey(cacheKey);
163+
await this.#persistContext(cacheKey, manager, keyId, context);
164+
return;
165+
}
166+
if (!current) {
167+
return;
168+
}
169+
current.lastUsed = new Date().toISOString();
170+
this.#contexts.set(cacheKey, current);
171+
await this.#store.set(CONTEXTS_KEY, Object.fromEntries(this.#contexts));
172+
}
173+
174+
#parseCacheKey(cacheKey: string): {
175+
context: KeyServiceContext;
176+
keyId: string;
177+
} {
178+
const [context, ...rest] = cacheKey.split(":");
179+
return {
180+
context: context as KeyServiceContext,
181+
keyId: rest.join(":"),
182+
};
183+
}
184+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export { GlobalState } from "./state";
22
export { runtime } from "./runtime.svelte";
3+
export { KeyService } from "./controllers/key";
4+
export type { KeyServiceContext } from "./controllers/key";

infrastructure/eid-wallet/src/lib/global/state.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Store } from "@tauri-apps/plugin-store";
22
import NotificationService from "../services/NotificationService";
33
import { VaultController } from "./controllers/evault";
4+
import { KeyService } from "./controllers/key";
45
import { SecurityController } from "./controllers/security";
56
import { UserController } from "./controllers/user";
67
/**
@@ -27,13 +28,15 @@ export class GlobalState {
2728
userController: UserController;
2829
vaultController: VaultController;
2930
notificationService: NotificationService;
31+
keyService: KeyService;
3032

31-
private constructor(store: Store) {
33+
private constructor(store: Store, keyService: KeyService) {
3234
this.#store = store;
3335
this.securityController = new SecurityController(store);
3436
this.userController = new UserController(store);
3537
this.vaultController = new VaultController(store, this.userController);
3638
this.notificationService = NotificationService.getInstance();
39+
this.keyService = keyService;
3740
}
3841

3942
/**
@@ -46,13 +49,64 @@ export class GlobalState {
4649
const store = await Store.load("global-state.json", {
4750
autoSave: true,
4851
});
52+
const keyService = new KeyService(store);
53+
await keyService.initialize();
4954
const alreadyInitialized = await store.get<boolean>("initialized");
5055

51-
const instance = new GlobalState(store);
56+
const instance = new GlobalState(store, keyService);
5257

5358
if (!alreadyInitialized) {
5459
await instance.#store.set("initialized", true);
60+
await instance.#store.set("isOnboardingComplete", false);
61+
await instance.keyService.setReady(false);
62+
} else {
63+
const onboardingFlag = await instance.#store.get<boolean>(
64+
"isOnboardingComplete",
65+
);
66+
if (onboardingFlag === undefined) {
67+
await instance.#store.set("isOnboardingComplete", false);
68+
await instance.keyService.setReady(false);
69+
} else {
70+
await instance.keyService.setReady(onboardingFlag);
71+
}
5572
}
5673
return instance;
5774
}
75+
76+
get isOnboardingComplete() {
77+
return this.#store
78+
.get<boolean>("isOnboardingComplete")
79+
.then((value) => value ?? false)
80+
.catch((error) => {
81+
console.error("Failed to get onboarding status:", error);
82+
return false;
83+
});
84+
}
85+
86+
set isOnboardingComplete(value: boolean | Promise<boolean>) {
87+
if (value instanceof Promise) {
88+
value
89+
.then((resolved) => {
90+
this.#store
91+
.set("isOnboardingComplete", resolved)
92+
.then(() => this.keyService.setReady(resolved))
93+
.catch((error) => {
94+
console.error(
95+
"Failed to set onboarding status:",
96+
error,
97+
);
98+
});
99+
})
100+
.catch((error) => {
101+
console.error("Failed to set onboarding status:", error);
102+
});
103+
} else {
104+
this.#store
105+
.set("isOnboardingComplete", value)
106+
.then(() => this.keyService.setReady(value))
107+
.catch((error) => {
108+
console.error("Failed to set onboarding status:", error);
109+
});
110+
}
111+
}
58112
}

0 commit comments

Comments
 (0)