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
346 lines
13 KiB
Rust
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),
|
|
}
|
|
}
|
|
|