Skip to content

[PM-24699] [BEEEP] Experiment with Rust-WASM compatible async-iterators #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 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 @@ -32,6 +32,7 @@ bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=1.0.0"
bitwarden-fido = { path = "crates/bitwarden-fido", version = "=1.0.0" }
bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0" }
bitwarden-ipc = { path = "crates/bitwarden-ipc", version = "=1.0.0" }
bitwarden-iter = { path = "crates/bitwarden-iter", version = "=1.0.0" }
bitwarden-send = { path = "crates/bitwarden-send", version = "=1.0.0" }
bitwarden-sm = { path = "bitwarden_license/bitwarden-sm", version = "=1.0.0" }
bitwarden-ssh = { path = "crates/bitwarden-ssh", version = "=1.0.0" }
Expand Down
11 changes: 11 additions & 0 deletions crates/bitwarden-fido/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ keywords.workspace = true

[features]
uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"]
wasm = [
"dep:wasm-bindgen",
"dep:wasm-bindgen-futures",
"dep:tsify",
"bitwarden-vault/wasm",
]

[dependencies]
async-trait = { workspace = true }
base64 = ">=0.22.1, <0.23"
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-error = { workspace = true }
bitwarden-iter = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true }
coset = ">=0.3.7, <0.4"
Expand All @@ -38,6 +46,9 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
uniffi = { workspace = true, optional = true }
uuid = { workspace = true }
tsify = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }

[lints]
workspace = true
30 changes: 29 additions & 1 deletion crates/bitwarden-fido/src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::sync::Mutex;

use bitwarden_core::{Client, VaultLockedError};
use bitwarden_crypto::CryptoError;
use bitwarden_vault::{CipherError, CipherView, EncryptionContext};
use bitwarden_error::bitwarden_error;
use bitwarden_iter::BwIterator;
use bitwarden_vault::{CipherError, CipherView, DecryptError, EncryptionContext};
use itertools::Itertools;
use log::error;
use passkey::{
Expand Down Expand Up @@ -84,8 +86,11 @@ pub enum SilentlyDiscoverCredentialsError {
}

#[allow(missing_docs)]
#[bitwarden_error(flat)]
#[derive(Debug, Error)]
pub enum CredentialsForAutofillError {
#[error(transparent)]
DecryptError(#[from] DecryptError),
#[error(transparent)]
CipherError(#[from] CipherError),
#[error(transparent)]
Expand Down Expand Up @@ -291,6 +296,29 @@ impl<'a> Fido2Authenticator<'a> {
.collect()
}

#[allow(missing_docs)]
pub async fn credentials_for_autofill_stream(
&mut self,
) -> Result<
BwIterator<Result<Fido2CredentialAutofillView, CredentialsForAutofillError>>,
CredentialsForAutofillError,
> {
let all_credentials = self.credential_store.all_credentials_stream().await?;

let iter = all_credentials
.into_iter()
.map(
|cipher| -> Result<Vec<Fido2CredentialAutofillView>, CredentialsForAutofillError> {
Ok(Fido2CredentialAutofillView::from_cipher_list_view(
&cipher?,
)?)
},
)
.flatten_ok();

Ok(BwIterator::new(iter))
}

pub(super) fn get_authenticator(
&self,
create_credential: bool,
Expand Down
5 changes: 5 additions & 0 deletions crates/bitwarden-fido/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ mod client_fido;
mod crypto;
mod traits;
mod types;

#[cfg(feature = "wasm")]
#[allow(missing_docs)]
pub mod wasm;

pub use authenticator::{
CredentialsForAutofillError, Fido2Authenticator, GetAssertionError, MakeCredentialError,
SilentlyDiscoverCredentialsError,
Expand Down
6 changes: 5 additions & 1 deletion crates/bitwarden-fido/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use bitwarden_vault::{CipherListView, CipherView, EncryptionContext, Fido2CredentialNewView};
use bitwarden_vault::{
CipherListView, CipherListViewIterator, CipherView, EncryptionContext, Fido2CredentialNewView,
};
use passkey::authenticator::UIHint;
use thiserror::Error;

Expand Down Expand Up @@ -47,6 +49,8 @@ pub trait Fido2CredentialStore: Send + Sync {
async fn all_credentials(&self) -> Result<Vec<CipherListView>, Fido2CallbackError>;

async fn save_credential(&self, cred: EncryptionContext) -> Result<(), Fido2CallbackError>;

async fn all_credentials_stream(&self) -> Result<CipherListViewIterator, Fido2CallbackError>;
}

#[allow(missing_docs)]
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-fido/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use passkey::types::webauthn::UserVerificationRequirement;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "wasm")]
use tsify::Tsify;

use super::{
get_enum_from_string_name, string_to_guid_bytes, InvalidGuid, SelectedCredential, UnknownEnum,
Expand All @@ -18,6 +20,7 @@ use super::{
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct Fido2CredentialAutofillView {
pub credential_id: Vec<u8>,
pub cipher_id: uuid::Uuid,
Expand Down
41 changes: 41 additions & 0 deletions crates/bitwarden-fido/src/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use bitwarden_iter::BwIterator;
use bitwarden_vault::CipherListViewIterator;
use itertools::Itertools;
use wasm_bindgen::prelude::*;

use crate::{CredentialsForAutofillError, Fido2CredentialAutofillView};

#[wasm_bindgen]
#[allow(missing_docs)]
pub struct Fido2CredentialAutofillViewIterator(
BwIterator<Result<Fido2CredentialAutofillView, CredentialsForAutofillError>>,
);

#[wasm_bindgen]
impl Fido2CredentialAutofillViewIterator {
#[allow(missing_docs)]
pub fn next(&mut self) -> Option<Fido2CredentialAutofillView> {
// Simplify the return because wasm_bindgen doesn't like Option<Result<T, E>> and I'm just
// proving the concept
self.0.iter.next().transpose().ok().flatten()
}
}

#[wasm_bindgen]
#[allow(missing_docs)]
pub async fn credentials_for_autofill_stream(
all_credentials: CipherListViewIterator,
) -> Result<Fido2CredentialAutofillViewIterator, CredentialsForAutofillError> {
let iter = all_credentials
.into_iter()
.map(
|cipher| -> Result<Vec<Fido2CredentialAutofillView>, CredentialsForAutofillError> {
Ok(Fido2CredentialAutofillView::from_cipher_list_view(
&cipher?,
)?)
},
)
.flatten_ok();

Ok(Fido2CredentialAutofillViewIterator(BwIterator::new(iter)))
}
2 changes: 2 additions & 0 deletions crates/bitwarden-iter/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
runner = 'wasm-bindgen-test-runner'
27 changes: 27 additions & 0 deletions crates/bitwarden-iter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "bitwarden-iter"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[package.metadata.cargo-udeps.ignore]
development = ["tokio-test"] # only used in doc-tests

[features]
wasm = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures"]

[dependencies]
futures = "0.3.31"
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }

[target.'cfg(target_arch="wasm32")'.dependencies]
wasm-bindgen-futures = { workspace = true, optional = true }

[lints]
workspace = true
3 changes: 3 additions & 0 deletions crates/bitwarden-iter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# bitwarden-iter

Utility crate for providing streamed calculation using iterators over Bitwarden data structures.
36 changes: 36 additions & 0 deletions crates/bitwarden-iter/src/iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![allow(missing_docs)]

use std::pin::Pin;

pub struct BwIterator<T> {
pub iter: Box<dyn Iterator<Item = T>>,
}

impl<T> BwIterator<T> {
pub fn new(iter: impl Iterator<Item = T> + 'static) -> Self {
Self {
iter: Box::new(iter),
}
}
}

impl<T> IntoIterator for BwIterator<T> {
type Item = T;
type IntoIter = Box<dyn Iterator<Item = T>>;

fn into_iter(self) -> Self::IntoIter {
self.iter
}
}

pub struct BwStream<T> {
pub stream: Pin<Box<dyn futures::stream::Stream<Item = T>>>,
}

impl<T> BwStream<T> {
pub fn new(stream: impl futures::stream::Stream<Item = T> + 'static) -> Self {
Self {
stream: Box::pin(stream),
}
}
}
7 changes: 7 additions & 0 deletions crates/bitwarden-iter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![doc = include_str!("../README.md")]

mod iter;

pub mod wasm;

pub use iter::{BwIterator, BwStream};
Loading
Loading