diff --git a/infrastructure/blindvote/src/core/voting-system.ts b/infrastructure/blindvote/src/core/voting-system.ts index 9e7997aa..80c86e49 100644 --- a/infrastructure/blindvote/src/core/voting-system.ts +++ b/infrastructure/blindvote/src/core/voting-system.ts @@ -20,7 +20,7 @@ import { dec, tallyOption } from "../crypto/pedersen"; -import { +import type { Voter, VoteData, ElectionConfig, diff --git a/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts b/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts index 838bd6ff..a8f59787 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts @@ -6,6 +6,7 @@ import { KeyManagerError, KeyManagerErrorCodes } from "./types"; /** * Factory class to create appropriate key managers based on context */ +// biome-ignore lint/complexity/noStaticOnlyClass: Factory pattern with state management requires a class export class KeyManagerFactory { private static hardwareKeyManager: HardwareKeyManager | null = null; private static softwareKeyManager: SoftwareKeyManager | null = null; @@ -16,18 +17,18 @@ export class KeyManagerFactory { static async getKeyManager(config: KeyManagerConfig): Promise { // If explicitly requesting hardware and not in pre-verification mode if (config.useHardware && !config.preVerificationMode) { - return this.getHardwareKeyManager(); + return KeyManagerFactory.getHardwareKeyManager(); } // If in pre-verification mode, always use software keys if (config.preVerificationMode) { console.log("Using software key manager for pre-verification mode"); - return this.getSoftwareKeyManager(); + return KeyManagerFactory.getSoftwareKeyManager(); } // Default behavior: try hardware first, fallback to software try { - const hardwareManager = this.getHardwareKeyManager(); + const hardwareManager = KeyManagerFactory.getHardwareKeyManager(); // Test if hardware is available by checking if we can call exists await hardwareManager.exists(config.keyId); console.log("Using hardware key manager"); @@ -36,7 +37,7 @@ export class KeyManagerFactory { console.log( "Hardware key manager not available, falling back to software", ); - return this.getSoftwareKeyManager(); + return KeyManagerFactory.getSoftwareKeyManager(); } } @@ -44,20 +45,20 @@ export class KeyManagerFactory { * Get hardware key manager instance (singleton) */ private static getHardwareKeyManager(): HardwareKeyManager { - if (!this.hardwareKeyManager) { - this.hardwareKeyManager = new HardwareKeyManager(); + if (!KeyManagerFactory.hardwareKeyManager) { + KeyManagerFactory.hardwareKeyManager = new HardwareKeyManager(); } - return this.hardwareKeyManager; + return KeyManagerFactory.hardwareKeyManager; } /** * Get software key manager instance (singleton) */ private static getSoftwareKeyManager(): SoftwareKeyManager { - if (!this.softwareKeyManager) { - this.softwareKeyManager = new SoftwareKeyManager(); + if (!KeyManagerFactory.softwareKeyManager) { + KeyManagerFactory.softwareKeyManager = new SoftwareKeyManager(); } - return this.softwareKeyManager; + return KeyManagerFactory.softwareKeyManager; } /** @@ -65,7 +66,7 @@ export class KeyManagerFactory { */ static async isHardwareAvailable(): Promise { try { - const hardwareManager = this.getHardwareKeyManager(); + const hardwareManager = KeyManagerFactory.getHardwareKeyManager(); // Try to check if a test key exists to verify hardware availability await hardwareManager.exists("test-hardware-check"); return true; @@ -88,14 +89,14 @@ export class KeyManagerFactory { preVerificationMode: context === "pre-verification", }; - return this.getKeyManager(config); + return KeyManagerFactory.getKeyManager(config); } /** * Reset singleton instances (useful for testing) */ static reset(): void { - this.hardwareKeyManager = null; - this.softwareKeyManager = null; + KeyManagerFactory.hardwareKeyManager = null; + KeyManagerFactory.softwareKeyManager = null; } } diff --git a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts index 75292053..0f7e069c 100644 --- a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts +++ b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts @@ -157,10 +157,7 @@ export class VaultController { } // Wait before retrying (exponential backoff) - const delay = Math.min( - 1000 * Math.pow(2, retryCount - 1), - 10000, - ); + const delay = Math.min(1000 * 2 ** (retryCount - 1), 10000); console.log(`Waiting ${delay}ms before resolve retry...`); await new Promise((resolve) => setTimeout(resolve, delay)); } @@ -174,9 +171,7 @@ export class VaultController { */ private async ensureClient(w3id: string): Promise { this.#endpoint = await this.resolveEndpoint(w3id); - this.#client = new GraphQLClient(this.#endpoint, { - timeout: 3000, // 3 second timeout for GraphQL requests - }); + this.#client = new GraphQLClient(this.#endpoint); return this.#client; } diff --git a/infrastructure/eid-wallet/src/lib/services/NotificationService.ts b/infrastructure/eid-wallet/src/lib/services/NotificationService.ts index 6998feb4..aff04897 100644 --- a/infrastructure/eid-wallet/src/lib/services/NotificationService.ts +++ b/infrastructure/eid-wallet/src/lib/services/NotificationService.ts @@ -17,7 +17,7 @@ export interface DeviceRegistration { export interface NotificationPayload { title: string; body: string; - data?: Record; + data?: Record; } class NotificationService { @@ -105,9 +105,8 @@ class NotificationService { this.deviceRegistration = registration; console.log("Device registered successfully:", registration); return true; - } else { - throw new Error(`Registration failed: ${response.statusText}`); } + throw new Error(`Registration failed: ${response.statusText}`); } catch (error) { console.error("Failed to register device:", error); return false; @@ -139,7 +138,6 @@ class NotificationService { body: payload.body, icon: "icons/32x32.png", sound: "default", - data: payload.data, }); await sendNotification({ @@ -147,7 +145,6 @@ class NotificationService { body: payload.body, icon: "icons/32x32.png", sound: "default", - data: payload.data, }); console.log("Notification sent successfully!"); @@ -183,11 +180,8 @@ class NotificationService { this.deviceRegistration = null; console.log("Device unregistered successfully"); return true; - } else { - throw new Error( - `Unregistration failed: ${response.statusText}`, - ); } + throw new Error(`Unregistration failed: ${response.statusText}`); } catch (error) { console.error("Failed to unregister device:", error); return false; @@ -370,7 +364,7 @@ class NotificationService { m.Store.load("global-state.json"), ); const vault = await store.get<{ ename: string }>("vault"); - return vault; + return vault || null; } catch (error) { console.error("Error getting vault eName:", error); return null; diff --git a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte index f3f083a4..d335b52e 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte @@ -22,6 +22,40 @@ import axios from "axios"; import { getContext, onDestroy, onMount } from "svelte"; import type { SVGAttributes } from "svelte/elements"; +// Type definitions for signing and deep link data +interface SigningData { + type?: string; + pollId?: string; + voteData?: Record; + userId?: string; + sessionId?: string; + message?: string; + redirect?: string; + platform_url?: string; + pollDetails?: { + title: string; + creatorName: string; + options: string[]; + }; + platformUrl?: string; +} + +interface DeepLinkData { + type: string; + platform?: string; + session?: string; + redirect?: string; + redirect_uri?: string; + data?: string; + pollId?: string; +} + +interface RevealedVoteData { + chosenOption: string; + pollId: string; + voterId?: string; +} + const globalState = getContext<() => GlobalState>("globalState")(); const pathProps: SVGAttributes = { stroke: "white", @@ -38,13 +72,13 @@ let loggedInDrawerOpen = $state(false); let signingDrawerOpen = $state(false); let scannedData: Scanned | undefined = $state(undefined); let scanning = false; -let loading = false; +let loading = $state(false); let redirect = $state(); let permissions_nullable: PermissionState | null; // Signing specific state let signingSessionId = $state(null); -let signingData = $state(null); +let signingData = $state(null); let isSigningRequest = $state(false); let showSigningSuccess = $state(false); @@ -60,7 +94,7 @@ let revealPollId = $state(null); let revealError = $state(null); let isRevealingVote = $state(false); let revealSuccess = $state(false); -let revealedVoteData = $state(null); +let revealedVoteData = $state(null); // Debug logging for selectedBlindVoteOption changes $effect(() => { @@ -565,16 +599,17 @@ async function handleSignVote() { } async function handleBlindVotingRequest( - blindVoteData: any, + blindVoteData: SigningData, platformUrl: string | null, redirectUri: string | null, ) { try { - console.log("🔍 Handling blind voting request:", blindVoteData); + const voteData = blindVoteData; + console.log("🔍 Handling blind voting request:", voteData); console.log("🔗 Platform URL:", platformUrl); // Extract poll details from the blind vote data - const pollId = blindVoteData.pollId; + const pollId = voteData.pollId; if (!pollId) { throw new Error("No poll ID provided in blind vote data"); } @@ -592,9 +627,9 @@ async function handleBlindVotingRequest( signingData = { pollId: pollId, pollDetails: pollDetails, - redirect: redirectUri, + redirect: redirectUri ?? undefined, sessionId: blindVoteData.sessionId, - platform_url: platformUrl, // Add the platform URL for API calls + platform_url: platformUrl ?? undefined, // Add the platform URL for API calls }; console.log("🔍 DEBUG: Stored signing data:", { @@ -631,7 +666,12 @@ async function handleBlindVote() { blindVoteError = null; isSubmittingBlindVote = true; - if (selectedBlindVoteOption === null || !signingData) { + if ( + selectedBlindVoteOption === null || + !signingData || + !signingData.pollId || + !signingData.pollDetails + ) { const errorMsg = "No vote option selected or poll details missing"; console.error("❌ Validation failed:", errorMsg); console.error("❌ selectedBlindVoteOption:", selectedBlindVoteOption); @@ -645,6 +685,10 @@ async function handleBlindVote() { return; } + // Extract validated values for type safety + const pollId = signingData.pollId; + const pollDetails = signingData.pollDetails; + try { // Get the vault for user identification const vault = await globalState.vaultController.vault; @@ -698,7 +742,7 @@ async function handleBlindVote() { // Check if user has already voted before attempting registration console.log("🔍 DEBUG: Checking if user has already voted..."); const voteStatusResponse = await axios.get( - `${platformUrl}/api/polls/${signingData.pollId}/vote?userId=${voterId}`, + `${platformUrl}/api/polls/${pollId}/vote?userId=${voterId}`, ); console.log("🔍 DEBUG: Vote status response:", voteStatusResponse.data); @@ -715,7 +759,7 @@ async function handleBlindVote() { // Register the voter on the backend first console.log("🔍 DEBUG: Registering voter on backend:", voterId); const registerResponse = await axios.post( - `${platformUrl}/api/votes/${signingData.pollId}/register`, + `${platformUrl}/api/votes/${pollId}/register`, { voterId: voterId, }, @@ -731,10 +775,10 @@ async function handleBlindVote() { // Create ElectionConfig from poll details const electionConfig = { - id: signingData.pollId, - title: signingData.pollDetails.title, + id: pollId, + title: pollDetails.title, description: "", - options: signingData.pollDetails.options.map( + options: pollDetails.options.map( (option: string, index: number) => `option_${index}`, ), maxVotes: 1, @@ -745,19 +789,11 @@ async function handleBlindVote() { // Create voting system and register voter locally const votingSystem = new VotingSystem(); - votingSystem.createElection( - signingData.pollId, - signingData.pollId, - electionConfig.options, - ); + votingSystem.createElection(pollId, pollId, electionConfig.options); // Register the voter locally (this creates the voter's key pair and anchor) console.log("🔍 DEBUG: Registering voter locally:", voterId); - votingSystem.registerVoter( - voterId, - signingData.pollId, - signingData.pollId, - ); + votingSystem.registerVoter(voterId, pollId, pollId); // Generate vote data with the selected option (PRIVACY PRESERVING - only known locally) const optionId = `option_${selectedBlindVoteOption}`; @@ -765,8 +801,8 @@ async function handleBlindVote() { const voteData = votingSystem.generateVoteData( voterId, - signingData.pollId, - signingData.pollId, + pollId, + pollId, optionId, ); @@ -793,12 +829,11 @@ async function handleBlindVote() { // Store vote data locally for later revelation // Convert BigInt values to strings for JSON serialization const localVoteData = { - pollId: signingData.pollId, + pollId: pollId, voterId: voterId, // Store voterId for ownership verification optionId: `option_${selectedBlindVoteOption}`, // Use the correct option ID format chosenOption: selectedBlindVoteOption, // Store the actual chosen option number - optionText: - signingData.pollDetails.options[selectedBlindVoteOption], // Store the actual option text + optionText: pollDetails.options[selectedBlindVoteOption], // Store the actual option text commitments: commitments, anchors: anchors, timestamp: new Date().toISOString(), @@ -812,11 +847,11 @@ async function handleBlindVote() { return value; }); - localStorage.setItem(`blindVote_${signingData.pollId}`, jsonString); + localStorage.setItem(`blindVote_${pollId}`, jsonString); // Submit to the API (PRIVACY PRESERVING - no chosenOptionId revealed) const payload = { - pollId: signingData.pollId, + pollId: pollId, voterId: voterId, // chosenOptionId is NOT sent - this preserves privacy! commitments: hexCommitments, @@ -850,9 +885,13 @@ async function handleBlindVote() { signingData.platform_url, ); - const redirectUrl = signingData.redirect.startsWith("http") + const redirectUrl = signingData.redirect?.startsWith("http") ? signingData.redirect - : `${signingData.platform_url}${signingData.redirect}`; + : `${signingData.platform_url}${signingData.redirect || ""}`; + + if (!redirectUrl) { + throw new Error("Redirect URL not provided"); + } console.log("🔍 DEBUG: Final redirect URL:", redirectUrl); console.log("🔍 Submitting blind vote to:", redirectUrl); @@ -951,41 +990,42 @@ onMount(async () => { console.log("Scan QR page mounted, checking for deep link data..."); // Function to handle deep link data - async function handleDeepLinkData(data: any) { - console.log("Handling deep link data:", data); - console.log("Data type:", data.type); - console.log("Platform:", data.platform); - console.log("Session:", data.session); - console.log("Redirect:", data.redirect); - console.log("Redirect URI:", data.redirect_uri); + async function handleDeepLinkData(data: DeepLinkData) { + const deepLinkData = data; + console.log("Handling deep link data:", deepLinkData); + console.log("Data type:", deepLinkData.type); + console.log("Platform:", deepLinkData.platform); + console.log("Session:", deepLinkData.session); + console.log("Redirect:", deepLinkData.redirect); + console.log("Redirect URI:", deepLinkData.redirect_uri); // Prevent duplicate processing by checking if we're already handling this type of request - if (data.type === "auth" && codeScannedDrawerOpen) { + if (deepLinkData.type === "auth" && codeScannedDrawerOpen) { console.log("Auth request already in progress, ignoring duplicate"); return; } - if (data.type === "sign" && signingDrawerOpen) { + if (deepLinkData.type === "sign" && signingDrawerOpen) { console.log( "Signing request already in progress, ignoring duplicate", ); return; } - if (data.type === "reveal" && isRevealRequest) { + if (deepLinkData.type === "reveal" && isRevealRequest) { console.log( "Reveal request already in progress, ignoring duplicate", ); return; } - if (data.type === "auth") { + if (deepLinkData.type === "auth") { console.log("Handling auth deep link"); // Handle auth deep link - platform = data.platform; - session = data.session; - redirect = data.redirect; + platform = deepLinkData.platform; + session = deepLinkData.session; + redirect = deepLinkData.redirect ?? null; try { - hostname = new URL(data.redirect).hostname; + hostname = new URL(deepLinkData.redirect || "").hostname; } catch (error) { console.error("Error parsing redirect URL:", error); hostname = "unknown"; @@ -1002,12 +1042,12 @@ onMount(async () => { "hostname:", hostname, ); - } else if (data.type === "sign") { + } else if (deepLinkData.type === "sign") { console.log("Handling signing deep link"); // Handle signing deep link - signingSessionId = data.session; - const base64Data = data.data; - const redirectUri = data.redirect_uri; + signingSessionId = deepLinkData.session ?? null; + const base64Data = deepLinkData.data; + const redirectUri = deepLinkData.redirect_uri; if (signingSessionId && base64Data && redirectUri) { redirect = redirectUri; @@ -1017,7 +1057,7 @@ onMount(async () => { console.log("Decoded signing data:", signingData); // Check if this is actually a blind voting request - if (signingData.type === "blind-vote") { + if (signingData && signingData.type === "blind-vote") { console.log( "🔍 Blind voting request detected in sign deep link", ); @@ -1030,13 +1070,13 @@ onMount(async () => { // Extract platform URL from the data const platformUrl = - signingData.platformUrl || + signingData?.platformUrl || "http://192.168.0.225:7777"; // Set up signingData for blind voting UI signingData = { - pollId: signingData.pollId, - sessionId: signingData.sessionId, + pollId: signingData?.pollId, + sessionId: signingData?.sessionId, platform_url: platformUrl, redirect: redirectUri, // Add the redirect URI from the deep link // We'll need to fetch poll details, but for now set a placeholder @@ -1080,10 +1120,10 @@ onMount(async () => { return; } } - } else if (data.type === "reveal") { + } else if (deepLinkData.type === "reveal") { console.log("Handling reveal deep link"); // Handle reveal deep link - const pollId = data.pollId; + const pollId = deepLinkData.pollId; if (pollId) { console.log("🔍 Reveal request for poll:", pollId); @@ -1290,10 +1330,13 @@ async function handleRevealVote() { // Don't close the drawer - let it show the revealed vote // The drawer content will change to show the success state - } catch (parseError: any) { + } catch (parseError: unknown) { console.error("❌ JSON Parse Error Details:", parseError); - console.error("❌ Parse Error Message:", parseError.message); - console.error("❌ Parse Error Stack:", parseError.stack); + console.error( + "❌ Parse Error Message:", + (parseError as Error).message, + ); + console.error("❌ Parse Error Stack:", (parseError as Error).stack); console.error("❌ Raw data that failed to parse:", storedVoteData); // Try to identify what went wrong @@ -1319,9 +1362,9 @@ async function handleRevealVote() { "Failed to parse stored vote data. The vote may be corrupted.", ); } - } catch (error: any) { + } catch (error: unknown) { console.error("❌ Error revealing vote:", error); - revealError = error.message || "Failed to reveal vote"; + revealError = (error as Error).message || "Failed to reveal vote"; } finally { isRevealingVote = false; } @@ -1617,9 +1660,9 @@ async function handleRevealVote() { -
- + Select your vote: {#each signingData.pollDetails?.options || [] as option, index} {/each} -
+