feature/7-add-wasm-api-in-sharenet-passport-for-creating-and-editing-passports #8

Merged
11 changed files with 80 additions and 10 deletions
Showing only changes of commit 8d0d203182 - Show all commits

View file

@ -80,6 +80,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -169,6 +170,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -217,7 +219,7 @@ where
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to load file: {}", e.into())))?;
// Decrypt file
let (seed, public_key, private_key, user_profiles) = self
let (seed, public_key, private_key, user_profiles, date_of_birth) = self
.file_encryptor
.decrypt(&passport_file, password)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to decrypt file: {}", e.into())))?;
@ -231,6 +233,7 @@ where
passport_file.univ_id.clone(),
);
passport.user_profiles = user_profiles;
passport.date_of_birth = date_of_birth;
// Re-encrypt and save if output path provided
if let Some(output_path) = output_path {
@ -243,6 +246,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to re-encrypt file: {}", e.into())))?;
@ -291,6 +295,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -377,6 +382,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -451,6 +457,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -506,6 +513,7 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;

View file

@ -46,6 +46,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result = create_profile_use_case.execute(
@ -104,6 +105,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result1 = create_profile_use_case.execute(
@ -133,6 +135,7 @@ mod tests {
language: Some("es".to_string()),
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
};
let result2 = create_profile_use_case.execute(
@ -185,6 +188,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
create_profile_use_case.execute(
@ -223,6 +227,7 @@ mod tests {
language: Some("es".to_string()),
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
};
let result = update_profile_use_case.execute(
@ -282,6 +287,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result = update_profile_use_case.execute(
@ -334,6 +340,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
create_profile_use_case.execute(

View file

@ -80,6 +80,7 @@ pub struct Passport {
pub did: Did,
pub univ_id: String,
pub user_profiles: Vec<UserProfile>,
pub date_of_birth: Option<DateOfBirth>,
}
impl Passport {
@ -108,6 +109,7 @@ impl Passport {
language: None,
notifications_enabled: true,
auto_sync: true,
show_date_of_birth: false,
},
);
@ -118,6 +120,7 @@ impl Passport {
did,
univ_id,
user_profiles: vec![default_profile],
date_of_birth: None,
}
}
@ -250,6 +253,14 @@ pub struct UserPreferences {
pub language: Option<String>,
pub notifications_enabled: bool,
pub auto_sync: bool,
pub show_date_of_birth: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateOfBirth {
pub month: u8,
pub day: u8,
pub year: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -298,4 +309,5 @@ pub struct PassportFile {
pub created_at: u64,
pub version: String,
pub enc_user_profiles: Vec<u8>, // Encrypted CBOR of Vec<UserProfile>
pub enc_date_of_birth: Vec<u8>, // Encrypted CBOR of Option<DateOfBirth>
}

View file

@ -75,6 +75,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let profile = UserProfile {
@ -120,6 +121,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: true,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -162,6 +164,7 @@ mod tests {
language: None,
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -195,6 +198,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -223,6 +227,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,

View file

@ -26,13 +26,14 @@ pub trait FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
) -> Result<PassportFile, Self::Error>;
fn decrypt(
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error>;
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>), Self::Error>;
}
pub trait FileStorage {

View file

@ -90,6 +90,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce
let mut salt = [0u8; SALT_LENGTH];
@ -118,6 +119,13 @@ impl FileEncryptor for XChaCha20FileEncryptor {
.encrypt(&nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Serialize and encrypt date of birth
let date_of_birth_bytes = serde_cbor::to_vec(&date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize date of birth: {}", e)))?;
let enc_date_of_birth = cipher
.encrypt(&nonce, &*date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth encryption failed: {}", e)))?;
// Get current timestamp
let created_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -136,6 +144,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
created_at,
version: "1.0.0".to_string(),
enc_user_profiles,
enc_date_of_birth,
})
}
@ -143,7 +152,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
@ -180,7 +189,14 @@ impl FileEncryptor for XChaCha20FileEncryptor {
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
// Decrypt date of birth
let date_of_birth_bytes = cipher
.decrypt(&nonce, &*file.enc_date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth decryption failed: {}", e)))?;
let date_of_birth: Option<DateOfBirth> = serde_cbor::from_slice(&date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize date of birth: {}", e)))?;
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
Ok((seed, public_key, private_key, user_profiles))
Ok((seed, public_key, private_key, user_profiles, date_of_birth))
}
}

View file

@ -45,7 +45,7 @@ mod tests {
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
@ -56,7 +56,7 @@ mod tests {
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
let (decrypted_seed, decrypted_public_key, _, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
@ -72,7 +72,7 @@ mod tests {
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");

View file

@ -91,6 +91,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce using WASM-compatible RNG
let mut salt = [0u8; SALT_LENGTH];
@ -120,6 +121,13 @@ impl FileEncryptor for XChaCha20FileEncryptor {
.encrypt(&nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Serialize and encrypt date of birth
let date_of_birth_bytes = serde_cbor::to_vec(&date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize date of birth: {}", e)))?;
let enc_date_of_birth = cipher
.encrypt(&nonce, &*date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth encryption failed: {}", e)))?;
// Get current timestamp using WASM-compatible time
let created_at = time::now_seconds()?;
@ -135,6 +143,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
created_at,
version: "1.0.0".to_string(),
enc_user_profiles,
enc_date_of_birth,
})
}
@ -142,7 +151,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
@ -179,7 +188,14 @@ impl FileEncryptor for XChaCha20FileEncryptor {
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
// Decrypt date of birth
let date_of_birth_bytes = cipher
.decrypt(&nonce, &*file.enc_date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth decryption failed: {}", e)))?;
let date_of_birth: Option<DateOfBirth> = serde_cbor::from_slice(&date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize date of birth: {}", e)))?;
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
Ok((seed, public_key, private_key, user_profiles))
Ok((seed, public_key, private_key, user_profiles, date_of_birth))
}
}

View file

@ -25,6 +25,7 @@ mod tests {
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
enc_date_of_birth: vec![],
};
// Save the file

View file

@ -114,7 +114,7 @@ pub fn import_from_encrypted_data(
// Decrypt the passport file using the password
let encryptor = XChaCha20FileEncryptor;
let (seed, public_key, private_key, user_profiles) = encryptor.decrypt(
let (seed, public_key, private_key, user_profiles, date_of_birth) = encryptor.decrypt(
&passport_file,
&password,
).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?;
@ -128,6 +128,7 @@ pub fn import_from_encrypted_data(
did,
univ_id: passport_file.univ_id,
user_profiles,
date_of_birth,
};
let result = serde_wasm_bindgen::to_value(&passport)
@ -155,6 +156,7 @@ pub fn export_to_encrypted_data(
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
).map_err(|e| JsValue::from_str(&format!("Failed to encrypt passport: {}", e)))?;
// Serialize to bytes for browser download

View file

@ -285,6 +285,7 @@ impl CliInterface {
language,
notifications_enabled: notifications,
auto_sync,
show_date_of_birth: false,
};
let create_use_case = CreateUserProfileUseCase::new(
@ -357,6 +358,7 @@ impl CliInterface {
language: language.or_else(|| existing_profile.preferences.language.clone()),
notifications_enabled: notifications.unwrap_or(existing_profile.preferences.notifications_enabled),
auto_sync: auto_sync.unwrap_or(existing_profile.preferences.auto_sync),
show_date_of_birth: existing_profile.preferences.show_date_of_birth,
};
// Clone values before using them in multiple places