Add additional tests
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run

This commit is contained in:
Continuist 2025-10-29 20:36:20 -04:00
parent 34b2a776fe
commit e7db02ab73
7 changed files with 203 additions and 634 deletions

View file

@ -52,7 +52,7 @@ jobs:
- name: Run WASM headless tests - name: Run WASM headless tests
run: | run: |
cd libs/sharenet-passport cd libs/sharenet-passport/tests/wasm-headless
wasm-pack test --headless --chrome --firefox --node wasm-pack test --headless --chrome --firefox --node
test-wasm-webdriver: test-wasm-webdriver:
@ -99,14 +99,25 @@ jobs:
mv geckodriver /usr/local/bin/ mv geckodriver /usr/local/bin/
chmod +x /usr/local/bin/geckodriver 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: | run: |
cd libs/sharenet-passport cd libs/sharenet-passport
# Build WASM package for testing
wasm-pack build --target web --out-dir pkg wasm-pack build --target web --out-dir pkg
# Run WebDriver tests (placeholder - implement actual WebDriver tests) - name: Install Rust WebDriver dependencies
echo "WebDriver tests would run here with Selenium/WebDriver" 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: build-wasm:
runs-on: [ci] runs-on: [ci]

135
Cargo.lock generated
View file

@ -139,16 +139,6 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@ -390,12 +380,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.9" version = "0.14.9"
@ -535,9 +519,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.81" version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@ -555,28 +539,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.6" version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
@ -734,15 +702,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 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]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.27"
@ -851,7 +810,6 @@ dependencies = [
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-bindgen-test",
"web-time", "web-time",
"zeroize", "zeroize",
] ]
@ -869,12 +827,6 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@ -1019,16 +971,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 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]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
@ -1046,9 +988,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.104" version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -1057,25 +999,11 @@ dependencies = [
"wasm-bindgen-shared", "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]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.54" version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -1086,9 +1014,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.104" version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1096,55 +1024,31 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.104" version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [ dependencies = [
"bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.104" version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [ dependencies = [
"unicode-ident", "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]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.81" version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -1160,15 +1064,6 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"

View file

@ -3,6 +3,10 @@ members = [
"libs/sharenet-passport", "libs/sharenet-passport",
"sharenet-passport-cli" "sharenet-passport-cli"
] ]
exclude = [
"libs/sharenet-passport/tests/wasm-headless",
"libs/sharenet-passport/tests/wasm-webdriver"
]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]

View file

@ -40,7 +40,7 @@ web-time = "1.1"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
js-sys = "0.3" js-sys = "0.3"
gloo-storage = "0.3" gloo-storage = "0.3"
wasm-bindgen = "0.2" wasm-bindgen = "0.2.105"
serde-wasm-bindgen = "0.6" serde-wasm-bindgen = "0.6"
serde_json = "1.0" serde_json = "1.0"
@ -49,10 +49,9 @@ serde_json = "1.0"
getrandom = { version = "0.2", features = ["std"] } getrandom = { version = "0.2", features = ["std"] }
uuid = { version = "1.10", features = ["v7", "rng"] } uuid = { version = "1.10", features = ["v7", "rng"] }
# Dev dependencies for WASM testing # Dev dependencies for testing
[dev-dependencies] [dev-dependencies]
tempfile = "3.8" tempfile = "3.8"
wasm-bindgen-test = "0.3"
[lib] [lib]
crate-type = ["cdylib", "rlib"] # Support both native and WASM crate-type = ["cdylib", "rlib"] # Support both native and WASM

View file

@ -7,10 +7,10 @@ pub mod domain;
pub mod application; pub mod application;
pub mod infrastructure; pub mod infrastructure;
#[cfg(target_arch = "wasm32")] #[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
pub mod wasm; pub mod wasm;
#[cfg(target_arch = "wasm32")] #[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
#[cfg(test)] #[cfg(test)]
pub mod wasm_test; pub mod wasm_test;

View file

@ -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 //! This module provides browser-compatible functions that work with in-memory data
//! via wasm-bindgen. //! 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 wasm_bindgen::prelude::*;
use crate::application::use_cases::{ use crate::application::use_cases::{
CreatePassportUseCase, CreatePassportUseCase,
ImportFromRecoveryUseCase, ImportFromRecoveryUseCase,
ImportFromFileUseCase,
ExportPassportUseCase, ExportPassportUseCase,
SignCardUseCase, SignCardUseCase,
CreateUserProfileUseCase, CreateUserProfileUseCase,
@ -18,106 +18,151 @@ use crate::infrastructure::{
Bip39MnemonicGenerator, Bip39MnemonicGenerator,
Ed25519KeyDeriver, Ed25519KeyDeriver,
XChaCha20FileEncryptor, XChaCha20FileEncryptor,
FileSystemStorage
}; };
use crate::domain::entities::{Passport, UserIdentity, UserPreferences}; use crate::domain::entities::{Passport, UserIdentity, UserPreferences, PassportFile, RecoveryPhrase, UserProfile, Seed};
use crate::domain::traits::{MnemonicGenerator, FileStorage}; use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
/// Create a new passport with the given universe ID and password /// Create a new passport with the given universe ID and password
/// ///
/// Returns a JSON string containing both the passport and recovery phrase /// 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] #[wasm_bindgen]
pub fn create_passport( pub fn create_passport(
univ_id: String, univ_id: String,
password: String, password: String,
output_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let use_case = CreatePassportUseCase::new( // For WASM, we need to create a passport in memory without file operations
Bip39MnemonicGenerator, // This is a simplified version that creates the passport structure directly
Ed25519KeyDeriver, let generator = Bip39MnemonicGenerator;
XChaCha20FileEncryptor, let key_deriver = Ed25519KeyDeriver;
FileSystemStorage,
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,
); );
match use_case.execute(&univ_id, &password, &output_path) {
Ok((passport, recovery_phrase)) => {
let result = serde_wasm_bindgen::to_value(&serde_json::json!({ let result = serde_wasm_bindgen::to_value(&serde_json::json!({
"passport": passport, "passport": passport,
"recovery_phrase": recovery_phrase "recovery_phrase": recovery_phrase
})).map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; })).map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) Ok(result)
} }
Err(e) => Err(JsValue::from_str(&format!("Error creating passport: {}", e))), Err(e) => Err(JsValue::from_str(&format!("Error deriving keys: {}", e))),
}
}
Err(e) => Err(JsValue::from_str(&format!("Error generating recovery phrase: {}", e))),
} }
} }
/// Import a passport from recovery phrase /// Import a passport from recovery phrase
/// Returns the imported passport as JSON
#[wasm_bindgen] #[wasm_bindgen]
pub fn import_from_recovery( pub fn import_from_recovery(
univ_id: String, univ_id: String,
recovery_words: Vec<String>, recovery_words: Vec<String>,
password: String, password: String,
output_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let use_case = ImportFromRecoveryUseCase::new( let generator = Bip39MnemonicGenerator;
Bip39MnemonicGenerator, let key_deriver = Ed25519KeyDeriver;
Ed25519KeyDeriver,
XChaCha20FileEncryptor, // Validate recovery phrase
FileSystemStorage, 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) let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) 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] #[wasm_bindgen]
pub fn import_from_file( pub fn import_from_encrypted_data(
file_path: String, encrypted_data: Vec<u8>,
password: String, password: String,
output_path: Option<String>,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let use_case = ImportFromFileUseCase::new( // Deserialize the encrypted passport file
XChaCha20FileEncryptor, let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data)
FileSystemStorage, .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) let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error importing from file: {}", e))),
}
} }
/// 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] #[wasm_bindgen]
pub fn export_passport( pub fn export_to_encrypted_data(
passport_json: JsValue, passport_json: JsValue,
password: String, password: String,
output_path: String, ) -> Result<Vec<u8>, JsValue> {
) -> Result<(), JsValue> {
let passport: Passport = serde_wasm_bindgen::from_value(passport_json) let passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let use_case = ExportPassportUseCase::new( let encryptor = XChaCha20FileEncryptor;
XChaCha20FileEncryptor,
FileSystemStorage,
);
match use_case.execute(&passport, &password, &output_path) { // Encrypt the passport data
Ok(()) => Ok(()), let passport_file = encryptor.encrypt(
Err(e) => Err(JsValue::from_str(&format!("Error exporting passport: {}", e))), &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 /// Sign a message with the passport's private key
@ -164,14 +209,13 @@ pub fn validate_recovery_phrase(recovery_words: Vec<String>) -> Result<bool, JsV
} }
/// Create a new user profile for a passport /// Create a new user profile for a passport
/// Returns the updated passport as JSON
#[wasm_bindgen] #[wasm_bindgen]
pub fn create_user_profile( pub fn create_user_profile(
passport_json: JsValue, passport_json: JsValue,
hub_did: Option<String>, hub_did: Option<String>,
identity_json: JsValue, identity_json: JsValue,
preferences_json: JsValue, preferences_json: JsValue,
password: String,
file_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .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) let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let use_case = CreateUserProfileUseCase::new( // Create new user profile and add to passport (in-memory operation)
XChaCha20FileEncryptor, let profile = UserProfile::new(hub_did, identity, preferences);
FileSystemStorage, 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) let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error creating user profile: {}", e))),
}
} }
/// Update an existing user profile /// Update an existing user profile
/// Returns the updated passport as JSON
#[wasm_bindgen] #[wasm_bindgen]
pub fn update_user_profile( pub fn update_user_profile(
passport_json: JsValue, passport_json: JsValue,
profile_id: String, profile_id: String,
identity_json: JsValue, identity_json: JsValue,
preferences_json: JsValue, preferences_json: JsValue,
password: String,
file_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .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) let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let use_case = UpdateUserProfileUseCase::new( // Update user profile directly in passport (in-memory operation)
XChaCha20FileEncryptor, let profile = UserProfile::new(None, identity, preferences);
FileSystemStorage, 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) let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error updating user profile: {}", e))),
}
} }
/// Delete a user profile /// Delete a user profile
/// Returns the updated passport as JSON
#[wasm_bindgen] #[wasm_bindgen]
pub fn delete_user_profile( pub fn delete_user_profile(
passport_json: JsValue, passport_json: JsValue,
profile_id: String, profile_id: String,
password: String,
file_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let use_case = DeleteUserProfileUseCase::new( // Delete user profile directly from passport (in-memory operation)
XChaCha20FileEncryptor, passport.remove_user_profile_by_id(&profile_id)
FileSystemStorage, .map_err(|e| JsValue::from_str(&format!("Error deleting user profile: {}", e)))?;
);
match use_case.execute(&mut passport, Some(&profile_id), &password, &file_path) {
Ok(()) => {
let result = serde_wasm_bindgen::to_value(&passport) let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result) Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error deleting user profile: {}", e))),
}
} }
/// Change passport password and re-encrypt file /// Change passport password
/// Returns the updated passport as JSON
#[wasm_bindgen] #[wasm_bindgen]
pub fn change_passport_password( pub fn change_passport_password(
_passport_json: JsValue, _passport_json: JsValue,
old_password: String, _old_password: String,
new_password: String, _new_password: String,
file_path: String,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
// Load passport from file with old password // Note: This function requires re-encryption which typically needs file operations
let use_case = ImportFromFileUseCase::new( // In a browser environment, you'd need to handle this differently
XChaCha20FileEncryptor, // For now, we'll return an error indicating this operation isn't supported
FileSystemStorage, Err(JsValue::from_str(
); "Password change requires file operations which are not supported in browser environment. "
))
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 /// Get passport metadata from encrypted data
/// This can extract public metadata without full decryption
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_passport_metadata( pub fn get_passport_metadata(
file_path: String, encrypted_data: Vec<u8>,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let file_storage = FileSystemStorage; // Deserialize the encrypted passport file
let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data)
let passport_file = file_storage.load(&file_path) .map_err(|e| JsValue::from_str(&format!("Failed to deserialize passport file: {}", e)))?;
.map_err(|e| JsValue::from_str(&format!("Error loading file: {}", e)))?;
let metadata = serde_json::json!({ let metadata = serde_json::json!({
"did": passport_file.did, "did": passport_file.did,
@ -305,7 +315,6 @@ pub fn get_passport_metadata(
"public_key": hex::encode(&passport_file.public_key), "public_key": hex::encode(&passport_file.public_key),
"created_at": passport_file.created_at, "created_at": passport_file.created_at,
"version": passport_file.version, "version": passport_file.version,
"file_size": std::mem::size_of_val(&passport_file)
}); });
let result = serde_wasm_bindgen::to_value(&metadata) let result = serde_wasm_bindgen::to_value(&metadata)
@ -313,14 +322,12 @@ pub fn get_passport_metadata(
Ok(result) Ok(result)
} }
/// Validate passport file integrity /// Validate passport file integrity from encrypted data
#[wasm_bindgen] #[wasm_bindgen]
pub fn validate_passport_file( pub fn validate_passport_file(
file_path: String, encrypted_data: Vec<u8>,
) -> Result<bool, JsValue> { ) -> Result<bool, JsValue> {
let file_storage = FileSystemStorage; match serde_cbor::from_slice::<PassportFile>(&encrypted_data) {
match file_storage.load(&file_path) {
Ok(passport_file) => { Ok(passport_file) => {
// Basic validation checks // Basic validation checks
let is_valid = !passport_file.enc_seed.is_empty() let is_valid = !passport_file.enc_seed.is_empty()
@ -335,3 +342,4 @@ pub fn validate_passport_file(
Err(_) => Ok(false), Err(_) => Ok(false),
} }
} }

View file

@ -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<String> = 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());
}
}
}