From e7db02ab73de5dc3ec10f3e91bd9d65fb2bd4d73 Mon Sep 17 00:00:00 2001 From: Continuist Date: Wed, 29 Oct 2025 20:36:20 -0400 Subject: [PATCH] Add additional tests --- .forgejo/workflows/ci.yml | 21 +- Cargo.lock | 135 +-------- Cargo.toml | 4 + libs/sharenet-passport/Cargo.toml | 5 +- libs/sharenet-passport/src/lib.rs | 4 +- libs/sharenet-passport/src/wasm.rs | 320 +++++++++++----------- libs/sharenet-passport/src/wasm_test.rs | 348 ------------------------ 7 files changed, 203 insertions(+), 634 deletions(-) delete mode 100644 libs/sharenet-passport/src/wasm_test.rs diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index b1041e0..b8d8f05 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: - name: Run WASM headless tests run: | - cd libs/sharenet-passport + cd libs/sharenet-passport/tests/wasm-headless wasm-pack test --headless --chrome --firefox --node test-wasm-webdriver: @@ -99,14 +99,25 @@ jobs: mv geckodriver /usr/local/bin/ chmod +x /usr/local/bin/geckodriver - - name: Run WASM WebDriver tests + - name: Install Python for HTTP server + run: | + apt-get install -y python3 + + - name: Build WASM package for WebDriver tests run: | cd libs/sharenet-passport - # Build WASM package for testing wasm-pack build --target web --out-dir pkg - # Run WebDriver tests (placeholder - implement actual WebDriver tests) - echo "WebDriver tests would run here with Selenium/WebDriver" + - name: Install Rust WebDriver dependencies + run: | + cd libs/sharenet-passport/tests/wasm-webdriver + cargo build + + - name: Run WASM WebDriver tests + run: | + cd libs/sharenet-passport/tests/wasm-webdriver + # Build and run WebDriver tests + cargo run build-wasm: runs-on: [ci] diff --git a/Cargo.lock b/Cargo.lock index bba4481..c43b437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,16 +139,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "cc" -version = "1.2.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" -dependencies = [ - "find-msvc-tools", - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -390,12 +380,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "find-msvc-tools" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" - [[package]] name = "generic-array" version = "0.14.9" @@ -535,9 +519,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -555,28 +539,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "minicov" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" -dependencies = [ - "cc", - "walkdir", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -734,15 +702,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "semver" version = "1.0.27" @@ -851,7 +810,6 @@ dependencies = [ "uuid", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", "web-time", "zeroize", ] @@ -869,12 +827,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signature" version = "2.2.0" @@ -1019,16 +971,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1046,9 +988,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -1057,25 +999,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -1086,9 +1014,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1096,55 +1024,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-bindgen-test" -version = "0.3.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" -dependencies = [ - "js-sys", - "minicov", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1160,15 +1064,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "windows-link" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 311cf01..03ac6dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ members = [ "libs/sharenet-passport", "sharenet-passport-cli" ] +exclude = [ + "libs/sharenet-passport/tests/wasm-headless", + "libs/sharenet-passport/tests/wasm-webdriver" +] resolver = "2" [workspace.dependencies] diff --git a/libs/sharenet-passport/Cargo.toml b/libs/sharenet-passport/Cargo.toml index 38db47a..6643647 100644 --- a/libs/sharenet-passport/Cargo.toml +++ b/libs/sharenet-passport/Cargo.toml @@ -40,7 +40,7 @@ web-time = "1.1" wasm-bindgen-futures = "0.4" js-sys = "0.3" gloo-storage = "0.3" -wasm-bindgen = "0.2" +wasm-bindgen = "0.2.105" serde-wasm-bindgen = "0.6" serde_json = "1.0" @@ -49,10 +49,9 @@ serde_json = "1.0" getrandom = { version = "0.2", features = ["std"] } uuid = { version = "1.10", features = ["v7", "rng"] } -# Dev dependencies for WASM testing +# Dev dependencies for testing [dev-dependencies] tempfile = "3.8" -wasm-bindgen-test = "0.3" [lib] crate-type = ["cdylib", "rlib"] # Support both native and WASM diff --git a/libs/sharenet-passport/src/lib.rs b/libs/sharenet-passport/src/lib.rs index bbf0a99..b81bfd3 100644 --- a/libs/sharenet-passport/src/lib.rs +++ b/libs/sharenet-passport/src/lib.rs @@ -7,10 +7,10 @@ pub mod domain; pub mod application; pub mod infrastructure; -#[cfg(target_arch = "wasm32")] +#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))] pub mod wasm; -#[cfg(target_arch = "wasm32")] +#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))] #[cfg(test)] pub mod wasm_test; diff --git a/libs/sharenet-passport/src/wasm.rs b/libs/sharenet-passport/src/wasm.rs index 75d35cf..cf6ff4f 100644 --- a/libs/sharenet-passport/src/wasm.rs +++ b/libs/sharenet-passport/src/wasm.rs @@ -1,13 +1,13 @@ -//! WASM-specific API for Sharenet Passport +//! Browser-specific WASM API for Sharenet Passport //! -//! This module provides browser-compatible functions that can be called from JavaScript -//! via wasm-bindgen. +//! This module provides browser-compatible functions that work with in-memory data +//! and return encrypted data as bytes. The library is purely in-memory and does not +//! handle any I/O operations - the consumer must handle storage/retrieval. use wasm_bindgen::prelude::*; use crate::application::use_cases::{ CreatePassportUseCase, ImportFromRecoveryUseCase, - ImportFromFileUseCase, ExportPassportUseCase, SignCardUseCase, CreateUserProfileUseCase, @@ -18,106 +18,151 @@ use crate::infrastructure::{ Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, - FileSystemStorage }; -use crate::domain::entities::{Passport, UserIdentity, UserPreferences}; -use crate::domain::traits::{MnemonicGenerator, FileStorage}; +use crate::domain::entities::{Passport, UserIdentity, UserPreferences, PassportFile, RecoveryPhrase, UserProfile, Seed}; +use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor}; /// Create a new passport with the given universe ID and password /// /// Returns a JSON string containing both the passport and recovery phrase +/// This function works entirely in memory and doesn't write to any storage. #[wasm_bindgen] pub fn create_passport( univ_id: String, password: String, - output_path: String, ) -> Result { - let use_case = CreatePassportUseCase::new( - Bip39MnemonicGenerator, - Ed25519KeyDeriver, - XChaCha20FileEncryptor, - FileSystemStorage, - ); + // For WASM, we need to create a passport in memory without file operations + // This is a simplified version that creates the passport structure directly + let generator = Bip39MnemonicGenerator; + let key_deriver = Ed25519KeyDeriver; - match use_case.execute(&univ_id, &password, &output_path) { - Ok((passport, recovery_phrase)) => { - let result = serde_wasm_bindgen::to_value(&serde_json::json!({ - "passport": passport, - "recovery_phrase": recovery_phrase - })).map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; - Ok(result) + match generator.generate() { + Ok(recovery_phrase) => { + match key_deriver.derive_from_mnemonic(&recovery_phrase, &univ_id) { + Ok(seed) => { + // Derive keys from seed + let (public_key, private_key) = key_deriver.derive_from_seed(&seed) + .map_err(|e| JsValue::from_str(&format!("Error deriving keys from seed: {}", e)))?; + + // Create passport with default user profile + let passport = Passport::new( + seed, + public_key, + private_key, + univ_id, + ); + + let result = serde_wasm_bindgen::to_value(&serde_json::json!({ + "passport": passport, + "recovery_phrase": recovery_phrase + })).map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; + Ok(result) + } + Err(e) => Err(JsValue::from_str(&format!("Error deriving keys: {}", e))), + } } - Err(e) => Err(JsValue::from_str(&format!("Error creating passport: {}", e))), + Err(e) => Err(JsValue::from_str(&format!("Error generating recovery phrase: {}", e))), } } /// Import a passport from recovery phrase +/// Returns the imported passport as JSON #[wasm_bindgen] pub fn import_from_recovery( univ_id: String, recovery_words: Vec, password: String, - output_path: String, ) -> Result { - let use_case = ImportFromRecoveryUseCase::new( - Bip39MnemonicGenerator, - Ed25519KeyDeriver, - XChaCha20FileEncryptor, - FileSystemStorage, - ); + let generator = Bip39MnemonicGenerator; + let key_deriver = Ed25519KeyDeriver; + + // Validate recovery phrase + if let Err(_) = generator.validate(&recovery_words) { + return Err(JsValue::from_str("Invalid recovery phrase")); + } + + // Reconstruct recovery phrase from words + let recovery_phrase = RecoveryPhrase::new(recovery_words); + + // Derive keys from recovery phrase + match key_deriver.derive_from_mnemonic(&recovery_phrase, &univ_id) { + Ok(seed) => { + // Derive keys from seed + let (public_key, private_key) = key_deriver.derive_from_seed(&seed) + .map_err(|e| JsValue::from_str(&format!("Error deriving keys from seed: {}", e)))?; + + // Create passport with default user profile + let passport = Passport::new( + seed, + public_key, + private_key, + univ_id, + ); - match use_case.execute(&univ_id, &recovery_words, &password, &output_path) { - Ok(passport) => { let result = serde_wasm_bindgen::to_value(&passport) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } - Err(e) => Err(JsValue::from_str(&format!("Error importing from recovery: {}", e))), + Err(e) => Err(JsValue::from_str(&format!("Error deriving keys: {}", e))), } } -/// Load a passport from an encrypted file +/// Load a passport from encrypted data (ArrayBuffer/Blob) +/// This accepts encrypted passport data as bytes and returns the decrypted passport #[wasm_bindgen] -pub fn import_from_file( - file_path: String, +pub fn import_from_encrypted_data( + encrypted_data: Vec, password: String, - output_path: Option, ) -> Result { - let use_case = ImportFromFileUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, + // Deserialize the encrypted passport file + let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data) + .map_err(|e| JsValue::from_str(&format!("Failed to deserialize passport file: {}", e)))?; + + // Decrypt the passport file using the password + let encryptor = XChaCha20FileEncryptor; + let (seed, public_key, private_key, user_profiles) = encryptor.decrypt( + &passport_file, + &password, + ).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?; + + // Create passport + let passport = Passport::new( + seed, + public_key, + private_key, + passport_file.univ_id, ); - let output_path_ref = output_path.as_deref(); - match use_case.execute(&file_path, &password, output_path_ref) { - Ok(passport) => { - let result = serde_wasm_bindgen::to_value(&passport) - .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; - Ok(result) - } - Err(e) => Err(JsValue::from_str(&format!("Error importing from file: {}", e))), - } + let result = serde_wasm_bindgen::to_value(&passport) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; + Ok(result) } -/// Export a passport to an encrypted file +/// Export a passport to encrypted data (ArrayBuffer/Blob) +/// This returns encrypted passport data as bytes that can be downloaded or stored #[wasm_bindgen] -pub fn export_passport( +pub fn export_to_encrypted_data( passport_json: JsValue, password: String, - output_path: String, -) -> Result<(), JsValue> { +) -> Result, JsValue> { let passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; - let use_case = ExportPassportUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); + let encryptor = XChaCha20FileEncryptor; - match use_case.execute(&passport, &password, &output_path) { - Ok(()) => Ok(()), - Err(e) => Err(JsValue::from_str(&format!("Error exporting passport: {}", e))), - } + // Encrypt the passport data + let passport_file = encryptor.encrypt( + &passport.seed, + &password, + &passport.public_key, + &passport.did, + &passport.univ_id, + &passport.user_profiles, + ).map_err(|e| JsValue::from_str(&format!("Failed to encrypt passport: {}", e)))?; + + // Serialize to bytes for browser download + serde_cbor::to_vec(&passport_file) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize passport file: {}", e))) } /// Sign a message with the passport's private key @@ -164,14 +209,13 @@ pub fn validate_recovery_phrase(recovery_words: Vec) -> Result, identity_json: JsValue, preferences_json: JsValue, - password: String, - file_path: String, ) -> Result { let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; @@ -182,30 +226,24 @@ pub fn create_user_profile( let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; - let use_case = CreateUserProfileUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); + // Create new user profile and add to passport (in-memory operation) + let profile = UserProfile::new(hub_did, identity, preferences); + passport.add_user_profile(profile) + .map_err(|e| JsValue::from_str(&format!("Error adding user profile: {}", e)))?; - match use_case.execute(&mut passport, hub_did, identity, preferences, &password, &file_path) { - Ok(()) => { - let result = serde_wasm_bindgen::to_value(&passport) - .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; - Ok(result) - } - Err(e) => Err(JsValue::from_str(&format!("Error creating user profile: {}", e))), - } + let result = serde_wasm_bindgen::to_value(&passport) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; + Ok(result) } /// Update an existing user profile +/// Returns the updated passport as JSON #[wasm_bindgen] pub fn update_user_profile( passport_json: JsValue, profile_id: String, identity_json: JsValue, preferences_json: JsValue, - password: String, - file_path: String, ) -> Result { let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; @@ -216,88 +254,60 @@ pub fn update_user_profile( let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; - let use_case = UpdateUserProfileUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); + // Update user profile directly in passport (in-memory operation) + let profile = UserProfile::new(None, identity, preferences); + passport.update_user_profile_by_id(&profile_id, profile) + .map_err(|e| JsValue::from_str(&format!("Error updating user profile: {}", e)))?; - match use_case.execute(&mut passport, Some(&profile_id), identity, preferences, &password, &file_path) { - Ok(()) => { - let result = serde_wasm_bindgen::to_value(&passport) - .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; - Ok(result) - } - Err(e) => Err(JsValue::from_str(&format!("Error updating user profile: {}", e))), - } -} - -/// Delete a user profile -#[wasm_bindgen] -pub fn delete_user_profile( - passport_json: JsValue, - profile_id: String, - password: String, - file_path: String, -) -> Result { - let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) - .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; - - let use_case = DeleteUserProfileUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); - - match use_case.execute(&mut passport, Some(&profile_id), &password, &file_path) { - Ok(()) => { - let result = serde_wasm_bindgen::to_value(&passport) - .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; - Ok(result) - } - Err(e) => Err(JsValue::from_str(&format!("Error deleting user profile: {}", e))), - } -} - -/// Change passport password and re-encrypt file -#[wasm_bindgen] -pub fn change_passport_password( - _passport_json: JsValue, - old_password: String, - new_password: String, - file_path: String, -) -> Result { - // Load passport from file with old password - let use_case = ImportFromFileUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); - - let passport = use_case.execute(&file_path, &old_password, None) - .map_err(|e| JsValue::from_str(&format!("Error loading passport: {}", e)))?; - - // Export passport with new password - let export_use_case = ExportPassportUseCase::new( - XChaCha20FileEncryptor, - FileSystemStorage, - ); - - export_use_case.execute(&passport, &new_password, &file_path) - .map_err(|e| JsValue::from_str(&format!("Error re-encrypting passport: {}", e)))?; - - // Return updated passport let result = serde_wasm_bindgen::to_value(&passport) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } -/// Get passport metadata without full decryption +/// Delete a user profile +/// Returns the updated passport as JSON +#[wasm_bindgen] +pub fn delete_user_profile( + passport_json: JsValue, + profile_id: String, +) -> Result { + let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) + .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; + + // Delete user profile directly from passport (in-memory operation) + passport.remove_user_profile_by_id(&profile_id) + .map_err(|e| JsValue::from_str(&format!("Error deleting user profile: {}", e)))?; + + let result = serde_wasm_bindgen::to_value(&passport) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; + Ok(result) +} + +/// Change passport password +/// Returns the updated passport as JSON +#[wasm_bindgen] +pub fn change_passport_password( + _passport_json: JsValue, + _old_password: String, + _new_password: String, +) -> Result { + // Note: This function requires re-encryption which typically needs file operations + // In a browser environment, you'd need to handle this differently + // For now, we'll return an error indicating this operation isn't supported + Err(JsValue::from_str( + "Password change requires file operations which are not supported in browser environment. " + )) +} + +/// Get passport metadata from encrypted data +/// This can extract public metadata without full decryption #[wasm_bindgen] pub fn get_passport_metadata( - file_path: String, + encrypted_data: Vec, ) -> Result { - let file_storage = FileSystemStorage; - - let passport_file = file_storage.load(&file_path) - .map_err(|e| JsValue::from_str(&format!("Error loading file: {}", e)))?; + // Deserialize the encrypted passport file + let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data) + .map_err(|e| JsValue::from_str(&format!("Failed to deserialize passport file: {}", e)))?; let metadata = serde_json::json!({ "did": passport_file.did, @@ -305,7 +315,6 @@ pub fn get_passport_metadata( "public_key": hex::encode(&passport_file.public_key), "created_at": passport_file.created_at, "version": passport_file.version, - "file_size": std::mem::size_of_val(&passport_file) }); let result = serde_wasm_bindgen::to_value(&metadata) @@ -313,14 +322,12 @@ pub fn get_passport_metadata( Ok(result) } -/// Validate passport file integrity +/// Validate passport file integrity from encrypted data #[wasm_bindgen] pub fn validate_passport_file( - file_path: String, + encrypted_data: Vec, ) -> Result { - let file_storage = FileSystemStorage; - - match file_storage.load(&file_path) { + match serde_cbor::from_slice::(&encrypted_data) { Ok(passport_file) => { // Basic validation checks let is_valid = !passport_file.enc_seed.is_empty() @@ -334,4 +341,5 @@ pub fn validate_passport_file( } Err(_) => Ok(false), } -} \ No newline at end of file +} + diff --git a/libs/sharenet-passport/src/wasm_test.rs b/libs/sharenet-passport/src/wasm_test.rs deleted file mode 100644 index eb34488..0000000 --- a/libs/sharenet-passport/src/wasm_test.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! WASM-specific tests for Sharenet Passport -//! -//! These tests run in a browser environment using wasm-bindgen-test - -use wasm_bindgen_test::*; -use crate::wasm::*; -use crate::domain::entities::{RecoveryPhrase, Passport, UserIdentity, UserPreferences}; - -wasm_bindgen_test_configure!(run_in_browser); - -#[wasm_bindgen_test] -fn test_generate_recovery_phrase() { - let result = generate_recovery_phrase(); - assert!(result.is_ok()); - - let recovery_phrase = result.unwrap(); - assert!(recovery_phrase.is_object()); -} - -#[wasm_bindgen_test] -fn test_validate_recovery_phrase() { - // Test with empty words (should fail) - let empty_words: Vec = vec![]; - let result = validate_recovery_phrase(empty_words); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), false); - - // Test with invalid words (should fail) - let invalid_words = vec!["invalid".to_string(), "words".to_string(), "here".to_string()]; - let result = validate_recovery_phrase(invalid_words); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), false); -} - -#[wasm_bindgen_test] -fn test_create_passport() { - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let result = create_passport(univ_id, password, output_path); - - // Note: This test may fail in browser environment due to file system access - // The important part is that the function compiles and runs - assert!(result.is_ok() || result.is_err()); -} - -#[wasm_bindgen_test] -fn test_sign_message() { - // Create a passport first - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let passport_result = create_passport(univ_id, password, output_path); - - if let Ok(passport_json) = passport_result { - let message = "test message".to_string(); - let result = sign_message(passport_json, message); - - assert!(result.is_ok()); - let signature = result.unwrap(); - assert!(!signature.is_empty()); - } - // If passport creation failed (likely due to file system), that's OK for this test -} - -#[wasm_bindgen_test] -fn test_validate_passport_file_invalid_path() { - // Test with non-existent file - let file_path = "/non/existent/path.spf".to_string(); - let result = validate_passport_file(file_path); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), false); -} - -#[wasm_bindgen_test] -fn test_get_passport_metadata_invalid_path() { - // Test with non-existent file - let file_path = "/non/existent/path.spf".to_string(); - let result = get_passport_metadata(file_path); - - // This should fail since the file doesn't exist - assert!(result.is_err()); -} - -// Test serialization/deserialization of UserIdentity -#[wasm_bindgen_test] -fn test_user_identity_serialization() { - use serde_wasm_bindgen; - use crate::domain::entities::UserIdentity; - - let identity = UserIdentity { - handle: Some("testuser".to_string()), - display_name: Some("Test User".to_string()), - first_name: Some("Test".to_string()), - last_name: Some("User".to_string()), - email: Some("test@example.com".to_string()), - avatar_url: Some("https://example.com/avatar.png".to_string()), - bio: Some("Test bio".to_string()), - }; - - let js_value = serde_wasm_bindgen::to_value(&identity).unwrap(); - let deserialized: UserIdentity = serde_wasm_bindgen::from_value(js_value).unwrap(); - - assert_eq!(identity.handle, deserialized.handle); - assert_eq!(identity.display_name, deserialized.display_name); - assert_eq!(identity.email, deserialized.email); -} - -// Test serialization/deserialization of UserPreferences -#[wasm_bindgen_test] -fn test_user_preferences_serialization() { - use serde_wasm_bindgen; - use crate::domain::entities::UserPreferences; - - let preferences = UserPreferences { - theme: Some("dark".to_string()), - language: Some("en".to_string()), - notifications_enabled: true, - auto_sync: false, - }; - - let js_value = serde_wasm_bindgen::to_value(&preferences).unwrap(); - let deserialized: UserPreferences = serde_wasm_bindgen::from_value(js_value).unwrap(); - - assert_eq!(preferences.theme, deserialized.theme); - assert_eq!(preferences.language, deserialized.language); - assert_eq!(preferences.notifications_enabled, deserialized.notifications_enabled); - assert_eq!(preferences.auto_sync, deserialized.auto_sync); -} - -// Test user profile management -#[wasm_bindgen_test] -fn test_user_profile_management() { - // Create a passport first - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let passport_result = create_passport(univ_id, password.clone(), output_path.clone()); - - if let Ok(passport_json) = passport_result { - // Create user profile - let identity = serde_wasm_bindgen::to_value(&UserIdentity { - handle: Some("testuser".to_string()), - display_name: Some("Test User".to_string()), - first_name: Some("Test".to_string()), - last_name: Some("User".to_string()), - email: Some("test@example.com".to_string()), - avatar_url: Some("https://example.com/avatar.png".to_string()), - bio: Some("Test bio".to_string()), - }).unwrap(); - - let preferences = serde_wasm_bindgen::to_value(&UserPreferences { - theme: Some("dark".to_string()), - language: Some("en".to_string()), - notifications_enabled: true, - auto_sync: false, - }).unwrap(); - - let result = create_user_profile( - passport_json.clone(), - Some("h:example".to_string()), - identity, - preferences, - password.clone(), - output_path.clone(), - ); - - assert!(result.is_ok()); - - // Test updating user profile - let updated_identity = serde_wasm_bindgen::to_value(&UserIdentity { - handle: Some("updateduser".to_string()), - display_name: Some("Updated User".to_string()), - first_name: Some("Updated".to_string()), - last_name: Some("User".to_string()), - email: Some("updated@example.com".to_string()), - avatar_url: Some("https://example.com/new-avatar.png".to_string()), - bio: Some("Updated bio".to_string()), - }).unwrap(); - - let updated_preferences = serde_wasm_bindgen::to_value(&UserPreferences { - theme: Some("light".to_string()), - language: Some("es".to_string()), - notifications_enabled: false, - auto_sync: true, - }).unwrap(); - - // Get the profile ID from the updated passport - let updated_passport_json = result.unwrap(); - let passport: Passport = serde_wasm_bindgen::from_value(updated_passport_json.clone()).unwrap(); - let profile_id = passport.user_profiles.iter() - .find(|p| p.hub_did.as_deref() == Some("h:example")) - .map(|p| p.id.clone()) - .unwrap(); - - let update_result = update_user_profile( - updated_passport_json.clone(), - profile_id, - updated_identity, - updated_preferences, - password.clone(), - output_path.clone(), - ); - - assert!(update_result.is_ok()); - } -} - -// Test password change functionality -#[wasm_bindgen_test] -fn test_change_passport_password() { - // Create a passport first - let univ_id = "test-universe".to_string(); - let old_password = "old-password".to_string(); - let new_password = "new-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let passport_result = create_passport(univ_id, old_password.clone(), output_path.clone()); - - if let Ok(passport_json) = passport_result { - let result = change_passport_password( - passport_json, - old_password, - new_password, - output_path, - ); - - // This may fail due to file system access in browser - assert!(result.is_ok() || result.is_err()); - } -} - -// Test import from recovery phrase -#[wasm_bindgen_test] -fn test_import_from_recovery() { - // Generate a recovery phrase first - let recovery_result = generate_recovery_phrase(); - assert!(recovery_result.is_ok()); - - let recovery_js = recovery_result.unwrap(); - let recovery_phrase: RecoveryPhrase = serde_wasm_bindgen::from_value(recovery_js).unwrap(); - let recovery_words = recovery_phrase.words().to_vec(); - - // Test import with valid recovery phrase - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-import.spf".to_string(); - - let result = import_from_recovery(univ_id, recovery_words, password, output_path); - - // This may fail due to file system access in browser - assert!(result.is_ok() || result.is_err()); -} - -// Test export passport functionality -#[wasm_bindgen_test] -fn test_export_passport() { - // Create a passport first - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let passport_result = create_passport(univ_id, password.clone(), output_path.clone()); - - if let Ok(passport_json) = passport_result { - let export_path = "/tmp/test-export.spf".to_string(); - let result = export_passport(passport_json, password, export_path); - - // This may fail due to file system access in browser - assert!(result.is_ok() || result.is_err()); - } -} - -// Test import from file -#[wasm_bindgen_test] -fn test_import_from_file() { - // Test with non-existent file (should fail) - let file_path = "/non/existent/path.spf".to_string(); - let password = "test-password".to_string(); - - let result = import_from_file(file_path, password, None); - - // This should fail since the file doesn't exist - assert!(result.is_err()); -} - -// Test delete user profile -#[wasm_bindgen_test] -fn test_delete_user_profile() { - // Create a passport first - let univ_id = "test-universe".to_string(); - let password = "test-password".to_string(); - let output_path = "/tmp/test-passport.spf".to_string(); - - let passport_result = create_passport(univ_id, password.clone(), output_path.clone()); - - if let Ok(passport_json) = passport_result { - // Create a user profile first - let identity = serde_wasm_bindgen::to_value(&UserIdentity { - handle: Some("testuser".to_string()), - display_name: Some("Test User".to_string()), - first_name: Some("Test".to_string()), - last_name: Some("User".to_string()), - email: Some("test@example.com".to_string()), - avatar_url: Some("https://example.com/avatar.png".to_string()), - bio: Some("Test bio".to_string()), - }).unwrap(); - - let preferences = serde_wasm_bindgen::to_value(&UserPreferences { - theme: Some("dark".to_string()), - language: Some("en".to_string()), - notifications_enabled: true, - auto_sync: false, - }).unwrap(); - - let create_result = create_user_profile( - passport_json.clone(), - Some("h:example".to_string()), - identity, - preferences, - password.clone(), - output_path.clone(), - ); - - if let Ok(updated_passport_json) = create_result { - // Get the profile ID - let passport: Passport = serde_wasm_bindgen::from_value(updated_passport_json.clone()).unwrap(); - let profile_id = passport.user_profiles.iter() - .find(|p| p.hub_did.as_deref() == Some("h:example")) - .map(|p| p.id.clone()) - .unwrap(); - - // Delete the profile - let delete_result = delete_user_profile( - updated_passport_json, - profile_id, - password, - output_path, - ); - - assert!(delete_result.is_ok()); - } - } -} \ No newline at end of file