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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,17 +15,17 @@ export class KeyManagerFactory {
* Get a key manager instance based on the configuration
*/
static async getKeyManager(config: KeyManagerConfig): Promise<KeyManager> {
// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware && !config.preVerificationMode) {
return KeyManagerFactory.getHardwareKeyManager();
}

// If in pre-verification mode, always use software keys
// If in pre-verification mode, ALWAYS use software keys (never hardware)
if (config.preVerificationMode) {
console.log("Using software key manager for pre-verification mode");
return KeyManagerFactory.getSoftwareKeyManager();
}

// If explicitly requesting hardware and not in pre-verification mode
if (config.useHardware) {
return KeyManagerFactory.getHardwareKeyManager();
}

// Default behavior: try hardware first, fallback to software
try {
const hardwareManager = KeyManagerFactory.getHardwareKeyManager();
Expand DownExpand Up@@ -82,11 +82,18 @@ export class KeyManagerFactory {
static async getKeyManagerForContext(
keyId: string,
context: "onboarding" | "signing" | "verification" | "pre-verification",
isFake: boolean,
): Promise<KeyManager> {
// Pre-verification users (isFake=true) or pre-verification context should NEVER use hardware
const shouldUseHardware =
!isFake &&
context !== "pre-verification" &&
(await KeyManagerFactory.isHardwareAvailable());

const config: KeyManagerConfig = {
keyId,
useHardware: context !== "pre-verification",
preVerificationMode: context === "pre-verification",
useHardware: shouldUseHardware,
preVerificationMode: context === "pre-verification" || isFake,
};

return KeyManagerFactory.getKeyManager(config);
Expand Down
35 changes: 33 additions & 2 deletions infrastructure/eid-wallet/src/lib/global/controllers/key.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import { KeyManagerFactory } from "$lib/crypto";
import type { KeyManager } from "$lib/crypto";
import type { Store } from "@tauri-apps/plugin-store";
import type { UserController } from "./user";

export type KeyServiceContext =
| "onboarding"
Expand DownExpand Up@@ -67,20 +68,50 @@ export class KeyService {
const cachedManager = this.#managerCache.get(cacheKey);
if (cachedManager) {
await this.#touchContext(cacheKey, cachedManager);
return cachedManager;
// If user is pre-verification, ensure we're using software keys
const isFake = await this.#isPreVerificationUser();
if (isFake && cachedManager.getType() === "hardware") {
// Force software keys for pre-verification users
this.#managerCache.delete(cacheKey);
} else {
return cachedManager;
}
}
this.#managerCache.delete(cacheKey);
}

const isFake = await this.#isPreVerificationUser();
// Force pre-verification mode if user is fake/pre-verification
const effectiveContext = isFake ? "pre-verification" : context;
const manager = await KeyManagerFactory.getKeyManagerForContext(
keyId,
context,
effectiveContext,
isFake ?? false,
);
this.#managerCache.set(cacheKey, manager);
await this.#persistContext(cacheKey, manager, keyId, context);
return manager;
}

/**
* Check if the current user is a pre-verification (demo) user
*/
async #isPreVerificationUser(): Promise<boolean> {
const isFake = await this.#store
.get<boolean>("fake")
.then((f) => {
if (!f) {
return false;
}
return f;
})
.catch((error) => {
console.error("Failed to get fake:", error);
return false;
});
return isFake;
}

async ensureKey(
keyId: string,
context: KeyServiceContext,
Expand Down
150 changes: 130 additions & 20 deletions infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,12 +23,59 @@ let verificationId = $state("");
let demoName = $state("");
let verificationSuccess = $state(false);
let keyManager: KeyManager | null = $state(null);
let showHardwareError = $state(false);
let checkingHardware = $state(false);
const KEY_ID = "default";

const handleGetStarted = async () => {
//get started functionality
isPaneOpen = true;
preVerified = false;
checkingHardware = true;
showHardwareError = false;
error = null;

try {
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}

// Actually try to generate a test hardware key
const testKeyId = `hardware-test-${Date.now()}`;
console.log(
"Testing hardware key generation with test key:",
testKeyId,
);

try {
const { manager, created } = await globalState.keyService.ensureKey(
testKeyId,
"onboarding",
);
console.log(
"Test key result - Manager type:",
manager.getType(),
"Created:",
created,
);

// Check if we got hardware manager and it actually created a key
if (manager.getType() !== "hardware") {
throw new Error("Got software fallback instead of hardware");
}

// Hardware works! Clean up test key and proceed
console.log("Hardware keys are working");
checkingHardware = false;
} catch (keyError) {
console.error("Hardware key test failed:", keyError);
showHardwareError = true;
checkingHardware = false;
}
} catch (err) {
console.error("Error checking hardware:", err);
showHardwareError = true;
checkingHardware = false;
}
Comment on lines +42 to +78

@coderabbitaicoderabbitaiBotNov 13, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop generating throwaway hardware keys during capability check

ensureKey actually runs the full hardware credential ceremony. That means the “capability check” now forces a biometric/PIN prompt, and any cancellation or timeout will be misread as “hardware unavailable”, pushing real hardware-capable users into the pre-verification path. On top of that, every click leaves behind a hardware-test-<timestamp> credential that we never clean up, which quickly pollutes the authenticator and can exhaust limited key slots. Please switch this probe to the existing isHardwareAvailable() call (or similar non-destructive check) and drop the throwaway key creation altogether.

Recommended change:

@@
- checkingHardware = true;- showHardwareError = false;- error = null;-- try {- if (!globalState) {- globalState = getContext<() => GlobalState>("globalState")();- }-- // Actually try to generate a test hardware key- const testKeyId = `hardware-test-${Date.now()}`;- console.log(- "Testing hardware key generation with test key:",- testKeyId,- );-- try {- const { manager, created } = await globalState.keyService.ensureKey(- testKeyId,- "onboarding",- );- console.log(- "Test key result - Manager type:",- manager.getType(),- "Created:",- created,- );-- // Check if we got hardware manager and it actually created a key- if (manager.getType() !== "hardware") {- throw new Error("Got software fallback instead of hardware");- }-- // Hardware works! Clean up test key and proceed- console.log("Hardware keys are working");- checkingHardware = false;- } catch (keyError) {- console.error("Hardware key test failed:", keyError);- showHardwareError = true;- checkingHardware = false;- }- } catch (err) {- console.error("Error checking hardware:", err);- showHardwareError = true;- checkingHardware = false;- }+ checkingHardware = true;+ showHardwareError = false;+ error = null;++ try {+ if (!globalState) {+ globalState = getContext<() => GlobalState>("globalState")();+ }++ const hardwareAvailable =+ await globalState.keyService.isHardwareAvailable();++ checkingHardware = false;+ showHardwareError = !hardwareAvailable;+ } catch (err) {+ console.error("Error checking hardware:", err);+ showHardwareError = true;+ checkingHardware = false;+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte around
lines 42-78, stop creating throwaway hardware keys with ensureKey because it
triggers the full credential ceremony and leaves persistent test credentials;
instead call the service's non-destructive probe (e.g., isHardwareAvailable() or
similar) to detect hardware capability, remove the testKeyId generation and
ensureKey invocation, and use the boolean result to set showHardwareError and
checkingHardware accordingly; also remove any catch branches specific to key
creation and only handle probe errors (set showHardwareError=true,
checkingHardware=false) without creating or attempting to clean up credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack, that's the only way to do it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the key service implementation to see what methods are available
echo "=== Searching for KeyService class definition ==="
ast-grep --pattern 'class KeyService {
$$$
}'
echo -e "\n=== Searching for isHardwareAvailable method ==="
rg -n "isHardwareAvailable" --type ts
echo -e "\n=== Searching for hardware capability check methods ==="
rg -nP "(checkHardware|isHardware|hardwareAvailable|canUseHardware)" --type ts -C2
echo -e "\n=== Finding KeyService file ==="
fd -e ts -e js "KeyService" --full-path
echo -e "\n=== Searching for KeyManager availability checks ==="
rg -n "getType\(\)" --type ts -g "*KeyManager*" -C2

Length of output: 21879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of isHardwareAvailable to understand what it checks
echo "=== KeyManagerFactory.isHardwareAvailable implementation ==="
ast-grep --pattern 'static async isHardwareAvailable(): Promise<boolean> {
$$$
}'
echo -e "\n=== Find the full KeyManagerFactory file ==="
fd "KeyManagerFactory.ts" --full-path
echo -e "\n=== Get lines 65-80 of KeyManagerFactory to see isHardwareAvailable ==="
find . -name "KeyManagerFactory.ts" -exec sed -n '65,80p' {} \;
echo -e "\n=== Check what getHardwareKeyManager does ==="
ast-grep --pattern 'static getHardwareKeyManager() {
$$$
}'

Length of output: 880


🏁 Script executed:

#!/bin/bash
# Check what the exists() method does in HardwareKeyManager
echo "=== HardwareKeyManager.exists() implementation ==="
cat infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts
echo -e "\n=== Check if exists() might trigger a credential ceremony ==="
rg -n "exists.*credential|exists.*prompt|exists.*biometric" --type ts -C3

Length of output: 4760


@coodos, understood — if generating a real test key is the only reliable way to verify hardware support, I'll defer to your experience with the platform.

That said, the test keys created with hardware-test-${Date.now()} are never cleaned up. Each "Get Started" click will leave behind a persistent credential in the hardware keystore, which could accumulate over time or exhaust limited key slots on some devices.

Would you consider adding cleanup logic after the hardware check completes (success or failure)? Something like:

try{const{ manager, created }=awaitglobalState.keyService.ensureKey(testKeyId,"onboarding",);// ... existing checks ...// Clean up test keyif(created&&manager.getType()==="hardware"){// Delete the test key if your KeyManager supports it// or track it for later cleanup}}catch(keyError){// ... existing error handling ...}

If the hardware API doesn't expose a delete/cleanup method, at minimum tracking these test key IDs for bulk cleanup later would prevent them from accumulating indefinitely.

};

const handlePreVerified = () => {
Expand DownExpand Up@@ -104,8 +151,24 @@ async function getApplicationPublicKey() {
}

const handleNext = async () => {
//handle next functionlity
goto("/verify");
// Initialize keys for onboarding context before going to verify
try {
loading = true;
if (!globalState) {
globalState = getContext<() => GlobalState>("globalState")();
}
await initializeKeyManager();
await ensureKeyForContext();
loading = false;
goto("/verify");
} catch (err) {
console.error("Failed to initialize keys for onboarding:", err);
error = "Failed to initialize security keys. Please try again.";
loading = false;
setTimeout(() => {
error = null;
}, 5000);
}
};

let globalState: GlobalState;
Expand DownExpand Up@@ -239,9 +302,13 @@ onMount(async () => {
>
</p>
<div class="flex justify-center whitespace-nowrap mt-1">
<ButtonAction class="w-full" callback={handleGetStarted}
>Get Started</ButtonAction
<ButtonAction
class="w-full"
callback={handleGetStarted}
disabled={checkingHardware}
>
{checkingHardware ? "Checking device..." : "Get Started"}
</ButtonAction>
</div>

<p class="mt-2 text-center">
Expand DownExpand Up@@ -304,20 +371,63 @@ onMount(async () => {
</div>
{/if}
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{#if checkingHardware}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Checking device capabilities...</h4>
</div>
</div>
{:else if showHardwareError}
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
Hardware Security Not Available
</h4>
<p class="text-black-700 mb-4">
Your phone doesn't support hardware crypto keys, which is a requirement for verified IDs.
</p>
<p class="text-black-700 mb-4">
Please use the pre-verification code option to create a demo account instead.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction
class="w-full"
callback={() => {
isPaneOpen = false;
handlePreVerified();
}}
>
Use Pre-Verification Code
</ButtonAction>
</div>
{:else}
{#if loading}
<div class="my-20">
<div
class="align-center flex w-full flex-col items-center justify-center gap-6"
>
<Shadow size={40} color="rgb(142, 82, 255);" />
<h4>Initializing security keys...</h4>
</div>
</div>
{:else}
<h4 class="mt-[2.3svh] mb-[0.5svh]">
Your Digital Self begins with the Real You
</h4>
<p class="text-black-700">
In the Web 3.0 Data Space, identity is linked to reality. We begin
by verifying your real-world passport, which serves as the
foundation for issuing your secure ePassport. At the same time, we
generate your eName – a unique digital identifier – and create your
eVault to store and protect your personal data.
</p>
<div class="flex justify-center whitespace-nowrap my-[2.3svh]">
<ButtonAction class="w-full" callback={handleNext}
>Next</ButtonAction
>
</div>
{/if}
{/if}
{/if}
</Drawer>
59 changes: 51 additions & 8 deletions infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,12 +109,41 @@ let hardwareKeyCheckComplete = $state(false);
const KEY_ID = "default";

async function handleVerification() {
const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
try {
// Ensure keys are initialized before starting verification
if (!keyManager) {
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (keyError) {
console.error("Failed to initialize keys:", keyError);
// If key initialization fails, go back to onboarding
await goto("/onboarding");
return;
}
}

const { data } = await axios.post(
new URL("/verification", PUBLIC_PROVISIONER_URL).toString(),
);
verificaitonId.set(data.id);
showVeriffModal = true;
watchEventStream(data.id);
} catch (error) {
console.error("Failed to start verification:", error);
// If verification fails due to key issues or any initialization error, go back to onboarding
const errorMessage =
error instanceof Error
? error.message.toLowerCase()
: String(error).toLowerCase();
if (
errorMessage.includes("key") ||
errorMessage.includes("initialize") ||
errorMessage.includes("manager")
) {
await goto("/onboarding");
}
}
}

function watchEventStream(id: string) {
Expand DownExpand Up@@ -227,9 +256,23 @@ onMount(async () => {
// Check hardware key support first
await checkHardwareKeySupport();

// If hardware is not available, redirect back to onboarding
if (!hardwareKeySupported) {
console.log("Hardware not available, redirecting to onboarding");
await goto("/onboarding");
return;
}

// Initialize key manager and check if default key pair exists
await initializeKeyManager();
await ensureKeyForVerification();
try {
await initializeKeyManager();
await ensureKeyForVerification();
} catch (error) {
console.error("Failed to initialize keys for verification:", error);
// If key initialization fails, redirect back to onboarding
await goto("/onboarding");
return;
}

handleContinue = async () => {
if ($status !== "approved" && $status !== "duplicate")
Expand Down