Skip to content

Commit

Permalink
Improve network account modules.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpfs committed Nov 28, 2023
1 parent 8e19a32 commit 1ace00a
Show file tree
Hide file tree
Showing 10 changed files with 532 additions and 502 deletions.
10 changes: 6 additions & 4 deletions workspace/net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ full = [
"client",
"server",
"hashcheck",
"listen",
"migrate",
"contacts",
"device",
"security-report",
]
client = [
"reqwest",
"dep:reqwest",
]
listen = ["dep:tokio-tungstenite"]
server = [
"toml",
"axum",
Expand All @@ -37,8 +39,8 @@ archive = ["sos-sdk/archive"]
contacts = ["sos-sdk/contacts"]
migrate = ["sos-migrate"]
device = [
"if-addrs",
"whoami",
"dep:if-addrs",
"dep:whoami",
]
security-report = ["sos-sdk/security-report"]
mem-fs = ["sos-sdk/mem-fs"]
Expand Down Expand Up @@ -81,6 +83,7 @@ tower = { version = "0.4", optional = true }
tower-http = { version = "0.3", features = ["cors", "trace"], optional = true }
async-stream = { version = "0.3", optional = true }
tokio-stream = { version = "0.1", optional = true }
tokio-tungstenite = { version = "0.20", features = ["rustls-tls-native-roots"] , optional = true}

# device
whoami = { version = "1.4", optional = true }
Expand All @@ -103,7 +106,6 @@ features = ["async"]
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
file-guard = "0.1"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "macros"] }
tokio-tungstenite = { version = "0.20", features = ["rustls-tls-native-roots"] }

[target.'cfg(target_arch="wasm32")'.dependencies]
tokio = { version = "1", features = ["rt", "time", "sync"] }
Expand Down
45 changes: 45 additions & 0 deletions workspace/net/src/client/account/archive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Adds backup archive functions to network account.
use super::network_account::LocalAccount;
use crate::client::{NetworkAccount, Result};
use sos_sdk::account::{
archive::{Inventory, RestoreOptions},
AccountInfo,
};
use std::path::{Path, PathBuf};
use tokio::io::{AsyncRead, AsyncSeek};

impl NetworkAccount {
/// Create a backup archive containing the
/// encrypted data for the account.
pub async fn export_backup_archive<P: AsRef<Path>>(
&self,
path: P,
) -> Result<()> {
Ok(self.account.export_backup_archive(path).await?)
}

/// Read the inventory from an archive.
pub async fn restore_archive_inventory<
R: AsyncRead + AsyncSeek + Unpin,
>(
buffer: R,
) -> Result<Inventory> {
Ok(LocalAccount::restore_archive_inventory(buffer).await?)
}

/// Import from an archive file.
pub async fn restore_backup_archive<P: AsRef<Path>>(
owner: Option<&mut NetworkAccount>,
path: P,
options: RestoreOptions,
data_dir: Option<PathBuf>,
) -> Result<AccountInfo> {
Ok(LocalAccount::restore_backup_archive(
owner.map(|o| &mut o.account),
path,
options,
data_dir,
)
.await?)
}
}
49 changes: 49 additions & 0 deletions workspace/net/src/client/account/contacts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Adds contacts functions to network account.
use crate::client::{NetworkAccount, Result};
use sos_sdk::{
account::contacts::ContactImportProgress,
vault::{secret::SecretId, Summary},
};
use std::path::Path;

impl NetworkAccount {
/// Get an avatar JPEG image for a contact in the current
/// open folder.
pub async fn load_avatar(
&mut self,
secret_id: &SecretId,
folder: Option<Summary>,
) -> Result<Option<Vec<u8>>> {
Ok(self.account.load_avatar(secret_id, folder).await?)
}

/// Export a contact secret to vCard file.
pub async fn export_vcard_file<P: AsRef<Path>>(
&mut self,
path: P,
secret_id: &SecretId,
folder: Option<Summary>,
) -> Result<()> {
Ok(self
.account
.export_vcard_file(path, secret_id, folder)
.await?)
}

/// Export all contacts to a single vCard.
pub async fn export_all_vcards<P: AsRef<Path>>(
&mut self,
path: P,
) -> Result<()> {
Ok(self.account.export_all_vcards(path).await?)
}

/// Import vCards from a string buffer.
pub async fn import_vcard(
&mut self,
content: &str,
progress: impl Fn(ContactImportProgress),
) -> Result<()> {
Ok(self.account.import_vcard(content, progress).await?)
}
}
22 changes: 17 additions & 5 deletions workspace/net/src/client/account/devices.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
//! User device manager.
//! Account device manager.
use crate::client::Result;
use std::path::PathBuf;

#[cfg(feature = "device")]
use crate::device::{self, TrustedDevice};
use crate::{
client::NetworkAccount,
device::{self, TrustedDevice},
};

/// Manages the devices for a user.
#[cfg(feature = "device")]
pub struct DeviceManager {
device_dir: PathBuf,
}

#[cfg(feature = "device")]
impl DeviceManager {
/// Create a new devices manager.
pub(super) fn new(device_dir: PathBuf) -> Result<Self> {
Expand Down Expand Up @@ -42,3 +42,15 @@ impl DeviceManager {
Ok(())
}
}

impl NetworkAccount {
/// Account devices reference.
pub fn devices(&self) -> &DeviceManager {
&self.devices
}

/// Account devices mutable reference.
pub fn devices_mut(&mut self) -> &mut DeviceManager {
&mut self.devices
}
}
110 changes: 110 additions & 0 deletions workspace/net/src/client/account/listen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Adds functions for listening to change notifications using
//! a websocket connection.
use crate::client::{
account::remote::{NetworkAccountReceiver, NetworkAccountSender},
Error, ListenOptions, NetworkAccount, Origin, RemoteBridge, Result,
WebSocketHandle,
};
use futures::{select, FutureExt};
use sos_sdk::prelude::SecureAccessKey;
use std::sync::Arc;

use super::network_account::LocalAccount;

impl NetworkAccount {
/// Listen for changes on a remote origin.
pub async fn listen(
&self,
origin: &Origin,
options: ListenOptions,
) -> Result<WebSocketHandle> {
let remotes = self.remotes.read().await;
if let Some(remote) = remotes.get(origin) {
if let Some(remote) =
remote.as_any().downcast_ref::<RemoteBridge>()
{
let remote = Arc::new(remote.clone());
let (handle, rx, tx) = RemoteBridge::listen(remote, options);
self.spawn_remote_bridge_channels(rx, tx);

// Store the listeners so we can
// close the connections on sign out
let mut listeners = self.listeners.lock().await;
listeners.push(handle.clone());

Ok(handle)
} else {
unreachable!();
}
} else {
Err(Error::OriginNotFound(origin.clone()))
}
}

fn spawn_remote_bridge_channels(
&self,
mut rx: NetworkAccountReceiver,
tx: NetworkAccountSender,
) {
if self.account.is_authenticated() {
let user = self.user().unwrap();
let keeper = user.identity().keeper();
let secret_key = user.identity().signer().to_bytes();

// TODO: needs shutdown hook so this loop exits
// TODO: when the websocket connection is closed
tokio::task::spawn(async move {
loop {
select!(
event = rx
.secure_access_key_rx
.recv()
.fuse() => {
if let Some((folder_id, secure_key)) = event {

// Decrypt the secure access key received
// when creating or importing a folder,
// must be done here as the remote bridge
// does not have access to the private key
// (account signing key)
let access_key = SecureAccessKey::decrypt(
&secure_key,
secret_key.clone(),
)
.await?;

// Save the access key for the synced folder
let identity = Arc::clone(&keeper);
LocalAccount::save_folder_password(
identity,
&folder_id,
access_key.clone(),
)
.await?;

tx.access_key_tx.send(access_key).await?;
}
}
event = rx
.remove_vault_rx
.recv()
.fuse() => {
if let Some(folder_id) = event {
// When a folder is removed via remote
// bridge changes we need to clean up the
// passphrase
let identity = Arc::clone(&keeper);
LocalAccount::remove_folder_password(
identity,
&folder_id,
)
.await?;
}
}
)
}
Ok::<(), Error>(())
});
}
}
}
33 changes: 33 additions & 0 deletions workspace/net/src/client/account/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Adds migration functions to network account.
use crate::client::{NetworkAccount, Result};
use sos_sdk::vault::Summary;
use std::path::Path;

#[cfg(feature = "migrate")]
use sos_migrate::{import::ImportTarget, AccountExport, AccountImport};

#[cfg(feature = "migrate")]
impl NetworkAccount {
/// Write a zip archive containing all the secrets
/// for the account unencrypted.
///
/// Used to migrate an account to another app.
pub async fn export_unsafe_archive<P: AsRef<Path>>(
&self,
path: P,
) -> Result<()> {
let migration = AccountExport::new(&self.account);
Ok(migration.export_unsafe_archive(path).await?)
}

/// Import secrets from another app.
#[cfg(feature = "migrate")]
pub async fn import_file(
&mut self,
target: ImportTarget,
) -> Result<Summary> {
let _ = self.sync_lock.lock().await;
let mut migration = AccountImport::new(&mut self.account);
Ok(migration.import_file(target).await?)
}
}
11 changes: 11 additions & 0 deletions workspace/net/src/client/account/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
//! Network aware account storage.

#[cfg(feature = "archive")]
mod archive;
#[cfg(feature = "contacts")]
mod contacts;
#[cfg(feature = "device")]
mod devices;
#[cfg(feature = "listen")]
mod listen;
mod macros;
#[cfg(feature = "migrate")]
mod migrate;
mod network_account;
mod remote;
#[cfg(feature = "security-report")]
mod security_report;
mod sync;

#[cfg(feature = "device")]
pub use devices::DeviceManager;
Expand Down
Loading

0 comments on commit 1ace00a

Please sign in to comment.