sharenet/passport/src/wasm.rs
continuist 05674b4caa
Some checks failed
Podman Rootless Demo / test-backend (push) Has been skipped
Podman Rootless Demo / test-frontend (push) Has been skipped
Podman Rootless Demo / build-backend (push) Has been skipped
Podman Rootless Demo / build-frontend (push) Failing after 5m32s
Podman Rootless Demo / deploy-prod (push) Has been skipped
Add to project
2025-11-01 11:53:11 -04:00

346 lines
13 KiB
Rust

//! 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, Did};
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<JsValue, JsValue> {
// 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<String>,
_password: String,
) -> Result<JsValue, JsValue> {
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<u8>,
password: String,
) -> Result<JsValue, JsValue> {
// 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, date_of_birth, default_user_profile_id) = encryptor.decrypt(
&passport_file,
&password,
).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?;
// Create passport with decrypted user profiles instead of creating a new default one
let did = Did::new(&public_key);
let passport = Passport {
seed,
public_key,
private_key,
did,
univ_id: passport_file.univ_id,
user_profiles,
date_of_birth,
default_user_profile_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<Vec<u8>, 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,
&passport.date_of_birth,
&passport.default_user_profile_id,
).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<Vec<u8>, 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<JsValue, JsValue> {
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<String>) -> Result<bool, JsValue> {
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<String>,
identity_json: JsValue,
preferences_json: JsValue,
) -> Result<JsValue, JsValue> {
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<JsValue, JsValue> {
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<JsValue, JsValue> {
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<JsValue, JsValue> {
// 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<u8>,
) -> Result<JsValue, JsValue> {
// 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<u8>,
) -> Result<bool, JsValue> {
match serde_cbor::from_slice::<PassportFile>(&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),
}
}