Add global date_of_birth field to passport
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
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:
parent
b0097ecb8b
commit
8d0d203182
11 changed files with 80 additions and 10 deletions
|
|
@ -80,6 +80,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
@ -169,6 +170,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.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())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to load file: {}", e.into())))?;
|
||||||
|
|
||||||
// Decrypt file
|
// 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
|
.file_encryptor
|
||||||
.decrypt(&passport_file, password)
|
.decrypt(&passport_file, password)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to decrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to decrypt file: {}", e.into())))?;
|
||||||
|
|
@ -231,6 +233,7 @@ where
|
||||||
passport_file.univ_id.clone(),
|
passport_file.univ_id.clone(),
|
||||||
);
|
);
|
||||||
passport.user_profiles = user_profiles;
|
passport.user_profiles = user_profiles;
|
||||||
|
passport.date_of_birth = date_of_birth;
|
||||||
|
|
||||||
// Re-encrypt and save if output path provided
|
// Re-encrypt and save if output path provided
|
||||||
if let Some(output_path) = output_path {
|
if let Some(output_path) = output_path {
|
||||||
|
|
@ -243,6 +246,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to re-encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to re-encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
@ -291,6 +295,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
@ -377,6 +382,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
@ -451,6 +457,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
@ -506,6 +513,7 @@ where
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
)
|
)
|
||||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = create_profile_use_case.execute(
|
let result = create_profile_use_case.execute(
|
||||||
|
|
@ -104,6 +105,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result1 = create_profile_use_case.execute(
|
let result1 = create_profile_use_case.execute(
|
||||||
|
|
@ -133,6 +135,7 @@ mod tests {
|
||||||
language: Some("es".to_string()),
|
language: Some("es".to_string()),
|
||||||
notifications_enabled: false,
|
notifications_enabled: false,
|
||||||
auto_sync: true,
|
auto_sync: true,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result2 = create_profile_use_case.execute(
|
let result2 = create_profile_use_case.execute(
|
||||||
|
|
@ -185,6 +188,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_profile_use_case.execute(
|
create_profile_use_case.execute(
|
||||||
|
|
@ -223,6 +227,7 @@ mod tests {
|
||||||
language: Some("es".to_string()),
|
language: Some("es".to_string()),
|
||||||
notifications_enabled: false,
|
notifications_enabled: false,
|
||||||
auto_sync: true,
|
auto_sync: true,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = update_profile_use_case.execute(
|
let result = update_profile_use_case.execute(
|
||||||
|
|
@ -282,6 +287,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = update_profile_use_case.execute(
|
let result = update_profile_use_case.execute(
|
||||||
|
|
@ -334,6 +340,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_profile_use_case.execute(
|
create_profile_use_case.execute(
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ pub struct Passport {
|
||||||
pub did: Did,
|
pub did: Did,
|
||||||
pub univ_id: String,
|
pub univ_id: String,
|
||||||
pub user_profiles: Vec<UserProfile>,
|
pub user_profiles: Vec<UserProfile>,
|
||||||
|
pub date_of_birth: Option<DateOfBirth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Passport {
|
impl Passport {
|
||||||
|
|
@ -108,6 +109,7 @@ impl Passport {
|
||||||
language: None,
|
language: None,
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: true,
|
auto_sync: true,
|
||||||
|
show_date_of_birth: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -118,6 +120,7 @@ impl Passport {
|
||||||
did,
|
did,
|
||||||
univ_id,
|
univ_id,
|
||||||
user_profiles: vec![default_profile],
|
user_profiles: vec![default_profile],
|
||||||
|
date_of_birth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,6 +253,14 @@ pub struct UserPreferences {
|
||||||
pub language: Option<String>,
|
pub language: Option<String>,
|
||||||
pub notifications_enabled: bool,
|
pub notifications_enabled: bool,
|
||||||
pub auto_sync: 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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -298,4 +309,5 @@ pub struct PassportFile {
|
||||||
pub created_at: u64,
|
pub created_at: u64,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub enc_user_profiles: Vec<u8>, // Encrypted CBOR of Vec<UserProfile>
|
pub enc_user_profiles: Vec<u8>, // Encrypted CBOR of Vec<UserProfile>
|
||||||
|
pub enc_date_of_birth: Vec<u8>, // Encrypted CBOR of Option<DateOfBirth>
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +75,7 @@ mod tests {
|
||||||
language: Some("en".to_string()),
|
language: Some("en".to_string()),
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let profile = UserProfile {
|
let profile = UserProfile {
|
||||||
|
|
@ -120,6 +121,7 @@ mod tests {
|
||||||
language: None,
|
language: None,
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: true,
|
auto_sync: true,
|
||||||
|
show_date_of_birth: false,
|
||||||
},
|
},
|
||||||
created_at: 1234567890,
|
created_at: 1234567890,
|
||||||
updated_at: 1234567890,
|
updated_at: 1234567890,
|
||||||
|
|
@ -162,6 +164,7 @@ mod tests {
|
||||||
language: None,
|
language: None,
|
||||||
notifications_enabled: false,
|
notifications_enabled: false,
|
||||||
auto_sync: true,
|
auto_sync: true,
|
||||||
|
show_date_of_birth: false,
|
||||||
},
|
},
|
||||||
created_at: 1234567890,
|
created_at: 1234567890,
|
||||||
updated_at: 1234567890,
|
updated_at: 1234567890,
|
||||||
|
|
@ -195,6 +198,7 @@ mod tests {
|
||||||
language: None,
|
language: None,
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
},
|
},
|
||||||
created_at: 1234567890,
|
created_at: 1234567890,
|
||||||
updated_at: 1234567890,
|
updated_at: 1234567890,
|
||||||
|
|
@ -223,6 +227,7 @@ mod tests {
|
||||||
language: None,
|
language: None,
|
||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
auto_sync: false,
|
auto_sync: false,
|
||||||
|
show_date_of_birth: false,
|
||||||
},
|
},
|
||||||
created_at: 1234567890,
|
created_at: 1234567890,
|
||||||
updated_at: 1234567890,
|
updated_at: 1234567890,
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,14 @@ pub trait FileEncryptor {
|
||||||
did: &Did,
|
did: &Did,
|
||||||
univ_id: &str,
|
univ_id: &str,
|
||||||
user_profiles: &[UserProfile],
|
user_profiles: &[UserProfile],
|
||||||
|
date_of_birth: &Option<DateOfBirth>,
|
||||||
) -> Result<PassportFile, Self::Error>;
|
) -> Result<PassportFile, Self::Error>;
|
||||||
|
|
||||||
fn decrypt(
|
fn decrypt(
|
||||||
&self,
|
&self,
|
||||||
file: &PassportFile,
|
file: &PassportFile,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error>;
|
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileStorage {
|
pub trait FileStorage {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
did: &Did,
|
did: &Did,
|
||||||
univ_id: &str,
|
univ_id: &str,
|
||||||
user_profiles: &[UserProfile],
|
user_profiles: &[UserProfile],
|
||||||
|
date_of_birth: &Option<DateOfBirth>,
|
||||||
) -> Result<PassportFile, Self::Error> {
|
) -> Result<PassportFile, Self::Error> {
|
||||||
// Generate salt and nonce
|
// Generate salt and nonce
|
||||||
let mut salt = [0u8; SALT_LENGTH];
|
let mut salt = [0u8; SALT_LENGTH];
|
||||||
|
|
@ -118,6 +119,13 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
.encrypt(&nonce, &*user_profiles_bytes)
|
.encrypt(&nonce, &*user_profiles_bytes)
|
||||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
|
.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
|
// Get current timestamp
|
||||||
let created_at = SystemTime::now()
|
let created_at = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
|
@ -136,6 +144,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
created_at,
|
created_at,
|
||||||
version: "1.0.0".to_string(),
|
version: "1.0.0".to_string(),
|
||||||
enc_user_profiles,
|
enc_user_profiles,
|
||||||
|
enc_date_of_birth,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +152,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
&self,
|
&self,
|
||||||
file: &PassportFile,
|
file: &PassportFile,
|
||||||
password: &str,
|
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
|
||||||
validate_file_format(&file.kdf, &file.cipher)?;
|
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)
|
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
|
||||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
|
.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
|
// 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ mod tests {
|
||||||
let password = "test-password";
|
let password = "test-password";
|
||||||
|
|
||||||
// Encrypt
|
// 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
|
// Verify file structure
|
||||||
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
||||||
|
|
@ -56,7 +56,7 @@ mod tests {
|
||||||
assert_eq!(encrypted_file.did, did.0);
|
assert_eq!(encrypted_file.did, did.0);
|
||||||
|
|
||||||
// Decrypt
|
// 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
|
// Verify decryption
|
||||||
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
||||||
|
|
@ -72,7 +72,7 @@ mod tests {
|
||||||
let did = Did::new(&public_key);
|
let did = Did::new(&public_key);
|
||||||
|
|
||||||
// Encrypt with one password
|
// 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
|
// Try to decrypt with wrong password
|
||||||
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
|
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
did: &Did,
|
did: &Did,
|
||||||
univ_id: &str,
|
univ_id: &str,
|
||||||
user_profiles: &[UserProfile],
|
user_profiles: &[UserProfile],
|
||||||
|
date_of_birth: &Option<DateOfBirth>,
|
||||||
) -> Result<PassportFile, Self::Error> {
|
) -> Result<PassportFile, Self::Error> {
|
||||||
// Generate salt and nonce using WASM-compatible RNG
|
// Generate salt and nonce using WASM-compatible RNG
|
||||||
let mut salt = [0u8; SALT_LENGTH];
|
let mut salt = [0u8; SALT_LENGTH];
|
||||||
|
|
@ -120,6 +121,13 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
.encrypt(&nonce, &*user_profiles_bytes)
|
.encrypt(&nonce, &*user_profiles_bytes)
|
||||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
|
.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
|
// Get current timestamp using WASM-compatible time
|
||||||
let created_at = time::now_seconds()?;
|
let created_at = time::now_seconds()?;
|
||||||
|
|
||||||
|
|
@ -135,6 +143,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
created_at,
|
created_at,
|
||||||
version: "1.0.0".to_string(),
|
version: "1.0.0".to_string(),
|
||||||
enc_user_profiles,
|
enc_user_profiles,
|
||||||
|
enc_date_of_birth,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +151,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
||||||
&self,
|
&self,
|
||||||
file: &PassportFile,
|
file: &PassportFile,
|
||||||
password: &str,
|
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
|
||||||
validate_file_format(&file.kdf, &file.cipher)?;
|
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)
|
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
|
||||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
|
.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
|
// 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ mod tests {
|
||||||
created_at: 1234567890,
|
created_at: 1234567890,
|
||||||
version: "1.0.0".to_string(),
|
version: "1.0.0".to_string(),
|
||||||
enc_user_profiles: vec![],
|
enc_user_profiles: vec![],
|
||||||
|
enc_date_of_birth: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save the file
|
// Save the file
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ pub fn import_from_encrypted_data(
|
||||||
|
|
||||||
// Decrypt the passport file using the password
|
// Decrypt the passport file using the password
|
||||||
let encryptor = XChaCha20FileEncryptor;
|
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,
|
&passport_file,
|
||||||
&password,
|
&password,
|
||||||
).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?;
|
).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?;
|
||||||
|
|
@ -128,6 +128,7 @@ pub fn import_from_encrypted_data(
|
||||||
did,
|
did,
|
||||||
univ_id: passport_file.univ_id,
|
univ_id: passport_file.univ_id,
|
||||||
user_profiles,
|
user_profiles,
|
||||||
|
date_of_birth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = serde_wasm_bindgen::to_value(&passport)
|
let result = serde_wasm_bindgen::to_value(&passport)
|
||||||
|
|
@ -155,6 +156,7 @@ pub fn export_to_encrypted_data(
|
||||||
&passport.did,
|
&passport.did,
|
||||||
&passport.univ_id,
|
&passport.univ_id,
|
||||||
&passport.user_profiles,
|
&passport.user_profiles,
|
||||||
|
&passport.date_of_birth,
|
||||||
).map_err(|e| JsValue::from_str(&format!("Failed to encrypt passport: {}", e)))?;
|
).map_err(|e| JsValue::from_str(&format!("Failed to encrypt passport: {}", e)))?;
|
||||||
|
|
||||||
// Serialize to bytes for browser download
|
// Serialize to bytes for browser download
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,7 @@ impl CliInterface {
|
||||||
language,
|
language,
|
||||||
notifications_enabled: notifications,
|
notifications_enabled: notifications,
|
||||||
auto_sync,
|
auto_sync,
|
||||||
|
show_date_of_birth: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let create_use_case = CreateUserProfileUseCase::new(
|
let create_use_case = CreateUserProfileUseCase::new(
|
||||||
|
|
@ -357,6 +358,7 @@ impl CliInterface {
|
||||||
language: language.or_else(|| existing_profile.preferences.language.clone()),
|
language: language.or_else(|| existing_profile.preferences.language.clone()),
|
||||||
notifications_enabled: notifications.unwrap_or(existing_profile.preferences.notifications_enabled),
|
notifications_enabled: notifications.unwrap_or(existing_profile.preferences.notifications_enabled),
|
||||||
auto_sync: auto_sync.unwrap_or(existing_profile.preferences.auto_sync),
|
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
|
// Clone values before using them in multiple places
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue