//! Browser-specific WASM API for Sharenet Passport //! //! 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::{ SignCardUseCase, }; use crate::infrastructure::{ Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, }; use crate::domain::entities::{Passport, UserIdentity, UserPreferences, PassportFile, RecoveryPhrase, UserProfile}; 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, ) -> Result { // 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 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 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, ) -> Result { 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, ); 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 deriving keys: {}", e))), } } /// 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_encrypted_data( encrypted_data: Vec, password: String, ) -> Result { // 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 result = serde_wasm_bindgen::to_value(&passport) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } /// 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_to_encrypted_data( passport_json: JsValue, password: String, ) -> Result, JsValue> { let passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; let encryptor = XChaCha20FileEncryptor; // 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 #[wasm_bindgen] pub fn sign_message( passport_json: JsValue, message: String, ) -> 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 = SignCardUseCase::new(); match use_case.execute(&passport, &message) { Ok(signature) => Ok(signature), Err(e) => Err(JsValue::from_str(&format!("Error signing message: {}", e))), } } /// Generate a new recovery phrase #[wasm_bindgen] pub fn generate_recovery_phrase() -> Result { let generator = Bip39MnemonicGenerator; match generator.generate() { Ok(recovery_phrase) => { let result = serde_wasm_bindgen::to_value(&recovery_phrase) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } Err(e) => Err(JsValue::from_str(&format!("Error generating recovery phrase: {}", e))), } } /// Validate a recovery phrase #[wasm_bindgen] pub fn validate_recovery_phrase(recovery_words: Vec) -> Result { let generator = Bip39MnemonicGenerator; match generator.validate(&recovery_words) { Ok(()) => Ok(true), Err(_) => Ok(false), } } /// Create a new user profile for a passport /// Returns the updated passport as JSON #[wasm_bindgen] pub fn create_user_profile( passport_json: JsValue, hub_did: Option, identity_json: JsValue, preferences_json: JsValue, ) -> Result { let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; let identity: UserIdentity = serde_wasm_bindgen::from_value(identity_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; // 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)))?; 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, ) -> Result { let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; let identity: UserIdentity = serde_wasm_bindgen::from_value(identity_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json) .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; // 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)))?; let result = serde_wasm_bindgen::to_value(&passport) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } /// 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( encrypted_data: Vec, ) -> Result { // 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, "univ_id": passport_file.univ_id, "public_key": hex::encode(&passport_file.public_key), "created_at": passport_file.created_at, "version": passport_file.version, }); let result = serde_wasm_bindgen::to_value(&metadata) .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?; Ok(result) } /// Validate passport file integrity from encrypted data #[wasm_bindgen] pub fn validate_passport_file( encrypted_data: Vec, ) -> Result { match serde_cbor::from_slice::(&encrypted_data) { Ok(passport_file) => { // Basic validation checks let is_valid = !passport_file.enc_seed.is_empty() && !passport_file.salt.is_empty() && !passport_file.nonce.is_empty() && !passport_file.public_key.is_empty() && !passport_file.did.is_empty() && !passport_file.univ_id.is_empty(); Ok(is_valid) } Err(_) => Ok(false), } }