Skip to content

Commit

Permalink
feat(device-loader): Impl for wasm32 using local storage
Browse files Browse the repository at this point in the history
- Implement device-loader for wasm32 using local storage web API.
- Update integration tests to be platform agnostic.
- Group all tests as single module (simplify configuring tests for
  wasm32)
  • Loading branch information
FirelightFlagboy committed Feb 18, 2025
1 parent 70bb6d4 commit 6186c21
Show file tree
Hide file tree
Showing 19 changed files with 889 additions and 104 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ dirs = { version = "5.0.1", default-features = false }
ed25519-dalek = { version = "2.1.1", default-features = false }
email-address-parser = { version = "2.0.0", default-features = false }
env_logger = { version = "0.10.2", default-features = false }
error_set = { version = "0.8.5", default-features = false }
event-listener = { version = "5.4.0", default-features = false }
eventsource-stream = { version = "0.2.3", default-features = false }
flate2 = { version = "1.0.35", features = ["rust_backend"], default-features = false }
Expand Down
3 changes: 3 additions & 0 deletions libparsec/crates/platform_device_loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ homepage.workspace = true
license.workspace = true
version.workspace = true
repository.workspace = true
autotests = false

[lints]
workspace = true
Expand Down Expand Up @@ -40,6 +41,8 @@ tokio = { workspace = true, features = ["fs"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { workspace = true, features = ["Window", "Storage"] }
serde_json = { workspace = true, features = ["std"] }
base64 = { workspace = true }
error_set = { workspace = true }

[dev-dependencies]
libparsec_tests_lite = { workspace = true }
Expand Down
10 changes: 4 additions & 6 deletions libparsec/crates/platform_device_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ use native as platform;
#[cfg(target_arch = "wasm32")]
use web as platform;

#[cfg(test)]
#[path = "../tests/mod.rs"]
mod tests;

pub const ARGON2ID_DEFAULT_MEMLIMIT_KB: u32 = 128 * 1024; // 128 Mo
pub const ARGON2ID_DEFAULT_OPSLIMIT: u32 = 3;
// Be careful when changing parallelism: libsodium only supports 1 thread !
Expand Down Expand Up @@ -316,7 +320,6 @@ pub async fn export_recovery_device(
(passphrase, file_content, recovery_device)
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn load_available_device_from_blob(
path: PathBuf,
blob: &[u8],
Expand Down Expand Up @@ -394,7 +397,6 @@ fn load_available_device_from_blob(
})
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn encrypt_device(device: &LocalDevice, key: &SecretKey) -> Bytes {
let mut cleartext = zeroize::Zeroizing::new(device.dump());
let ciphertext = key.encrypt(&cleartext);
Expand Down Expand Up @@ -428,7 +430,6 @@ impl From<DecryptDeviceFileError> for ChangeAuthentificationError {
}
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn decrypt_device_file(
device_file: &DeviceFile,
key: &SecretKey,
Expand All @@ -442,7 +443,6 @@ fn decrypt_device_file(
res
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn new_default_pbkdf_algo() -> DeviceFilePasswordAlgorithm {
DeviceFilePasswordAlgorithm::Argon2id {
memlimit_kb: ARGON2ID_DEFAULT_MEMLIMIT_KB.into(),
Expand All @@ -452,7 +452,6 @@ fn new_default_pbkdf_algo() -> DeviceFilePasswordAlgorithm {
}
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn secret_key_from_password(
password: &Password,
algorithm: &DeviceFilePasswordAlgorithm,
Expand All @@ -473,7 +472,6 @@ fn secret_key_from_password(
}
}

#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
fn server_url_from_device(device: &LocalDevice) -> String {
ParsecAddr::new(
device.organization_addr.hostname().to_owned(),
Expand Down
205 changes: 205 additions & 0 deletions libparsec/crates/platform_device_loader/src/web/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

use libparsec_types::{anyhow, thiserror};
use web_sys::wasm_bindgen::JsValue;

#[derive(Debug, thiserror::Error)]
pub enum NewStorageError {
#[error("No window context available, are we in a browser?")]
NoWindow,
#[error("No local storage available")]
NoLocalStorage,
#[error("Failed to access local storage ({:?})", .0)]
WindowError(JsValue),
}

macro_rules! impl_from_new_storage_error {
($for:ty) => {
impl From<NewStorageError> for $for {
fn from(value: NewStorageError) -> Self {
Self::Internal(anyhow::anyhow!("{value}"))
}
}
};
($($for:ty),*) => {
$(impl_from_new_storage_error!($for);)*
};
}

impl_from_new_storage_error!(
crate::LoadDeviceError,
crate::SaveDeviceError,
crate::ChangeAuthentificationError,
crate::ArchiveDeviceError,
crate::RemoveDeviceError
);

error_set::error_set! {
GetItemStorageError = {
#[display("Failed to get item {key} from storage ({error:?})")]
GetItemStorage {
key: String,
error: JsValue
}
};
SetItemStorageError = {
#[display("Failed to set item {key} to storage ({error:?})")]
SetItemStorage {
key: String,
error: JsValue
}
};
RemoveItemStorageError = {
#[display("Failed to remove item {key} from storage ({error:?})")]
RemoveItemStorage {
key: String,
error: JsValue
}
};
JsonDeserializationError = {
JsonDecode(serde_json::Error)
};
JsonSerError = {
JsonEncode(serde_json::Error)
};
RmpDecodeError = {
RmpDecode(libparsec_types::RmpDecodeError)
};
Base64DecodeError = {
B64Decode(base64::DecodeError)
};
ListAvailableDevicesError = GetItemStorageError
|| JsonDeserializationError
|| InvalidPathError
|| LoadAvailableDeviceError;
GetRawDeviceError = GetItemStorageError
|| Base64DecodeError
|| {
#[display("No device for '{key}'")]
Missing {
key: String
},
};
LoadAvailableDeviceError = GetRawDeviceError || RmpDecodeError;
InvalidPathError = {
#[display("Invalid path {}", path.display())]
InvalidPath {
path: std::path::PathBuf
}
};
LoadDeviceError = InvalidPathError
|| GetRawDeviceError
|| RmpDecodeError
|| {
InvalidFileType,
GetSecretKey(libparsec_crypto::CryptoError),
DecryptAndLoad(crate::DecryptDeviceFileError),
};
SaveDeviceError = SaveDeviceFileError || InvalidPathError;
SaveDeviceFileError = SetItemStorageError || AddItemToListError;
AddItemToListError = GetRawDeviceError
|| SetItemStorageError
|| JsonDeserializationError
|| JsonSerError;
RemoveItemFromListError = GetRawDeviceError
|| SetItemStorageError
|| JsonDeserializationError
|| JsonSerError;
ArchiveDeviceError = GetItemStorageError
|| AddItemToListError
|| RemoveItemFromListError
|| JsonDeserializationError
|| InvalidPathError;
RemoveDeviceError = GetItemStorageError
|| RemoveItemFromListError
|| RemoveItemStorageError
|| JsonDeserializationError
|| InvalidPathError;
}

impl From<LoadDeviceError> for crate::LoadDeviceError {
fn from(value: LoadDeviceError) -> Self {
match value {
LoadDeviceError::InvalidFileType => Self::InvalidData,
LoadDeviceError::GetSecretKey(_) => Self::DecryptionFailed,
LoadDeviceError::DecryptAndLoad(e) => e.into(),
LoadDeviceError::InvalidPath { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
LoadDeviceError::Missing { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
LoadDeviceError::GetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
LoadDeviceError::RmpDecode(_) => Self::InvalidData,
LoadDeviceError::B64Decode(_) => Self::InvalidData,
}
}
}

impl From<SaveDeviceError> for crate::SaveDeviceError {
fn from(value: SaveDeviceError) -> Self {
match value {
SaveDeviceError::SetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::Missing { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
SaveDeviceError::InvalidPath { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
SaveDeviceError::GetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::JsonDecode(_) => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::JsonEncode(_) => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::B64Decode(_) => Self::Internal(anyhow::anyhow!("{value}")),
}
}
}

impl From<LoadDeviceError> for crate::ChangeAuthentificationError {
fn from(value: LoadDeviceError) -> Self {
match value {
LoadDeviceError::InvalidFileType => Self::InvalidData,
LoadDeviceError::GetSecretKey(_) => Self::DecryptionFailed,
LoadDeviceError::DecryptAndLoad(e) => e.into(),
LoadDeviceError::InvalidPath { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
LoadDeviceError::Missing { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
LoadDeviceError::GetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
LoadDeviceError::RmpDecode(_) => Self::InvalidData,
LoadDeviceError::B64Decode(_) => Self::InvalidData,
}
}
}

impl From<SaveDeviceError> for crate::ChangeAuthentificationError {
fn from(value: SaveDeviceError) -> Self {
match value {
SaveDeviceError::SetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::Missing { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
SaveDeviceError::InvalidPath { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
SaveDeviceError::GetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::JsonDecode(_) => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::JsonEncode(_) => Self::Internal(anyhow::anyhow!("{value}")),
SaveDeviceError::B64Decode(_) => Self::InvalidData,
}
}
}

impl From<RemoveDeviceError> for crate::ChangeAuthentificationError {
fn from(value: RemoveDeviceError) -> Self {
match value {
RemoveDeviceError::GetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
RemoveDeviceError::Missing { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
RemoveDeviceError::SetItemStorage { .. } => Self::Internal(anyhow::anyhow!("{value}")),
RemoveDeviceError::JsonDecode(_) => Self::InvalidData,
RemoveDeviceError::JsonEncode(_) => Self::InvalidData,
RemoveDeviceError::RemoveItemStorage { .. } => {
Self::Internal(anyhow::anyhow!("{value}"))
}
RemoveDeviceError::InvalidPath { .. } => Self::InvalidPath(anyhow::anyhow!("{value}")),
RemoveDeviceError::B64Decode(_) => Self::InvalidData,
}
}
}

impl From<RemoveDeviceError> for crate::RemoveDeviceError {
fn from(value: RemoveDeviceError) -> Self {
Self::Internal(anyhow::anyhow!("{value}"))
}
}

impl From<ArchiveDeviceError> for crate::ArchiveDeviceError {
fn from(value: ArchiveDeviceError) -> Self {
Self::Internal(anyhow::anyhow!("{value}"))
}
}
Loading

0 comments on commit 6186c21

Please sign in to comment.