Skip to content

Commit d33fce5

Browse files
authored
Improve sync logic for deleted folders. (#479)
* Improve sync logic for deleted folders. * Bump probly-search dependency. * Move features to account_extras crate. Closes #466. * Update lock file. * Bump patch version.
1 parent f7167ef commit d33fce5

File tree

37 files changed

+570
-451
lines changed

37 files changed

+570
-451
lines changed

Cargo.lock

Lines changed: 201 additions & 193 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "2"
33
members = [
4+
"crates/account_extras",
45
"crates/artifact",
56
"crates/cli_helpers",
67
"crates/keychain_parser",
@@ -11,7 +12,7 @@ members = [
1112
"crates/server",
1213
"crates/sos",
1314
"crates/test_utils",
14-
"crates/vfs",
15+
"crates/vfs"
1516
]
1617

1718
[workspace.dependencies]

crates/account_extras/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "sos-account-extras"
3+
version = "0.14.5"
4+
edition = "2021"
5+
description = "Extra features for Save Our Secrets local accounts."
6+
homepage = "https://saveoursecrets.com"
7+
license = "MIT OR Apache-2.0"
8+
repository = "https://github.com/saveoursecrets/sdk"
9+
10+
[features]
11+
preferences = []
12+
security-report = []
13+
system-messages = []
14+
15+
[dependencies]
16+
thiserror.workspace = true
17+
serde.workspace = true
18+
serde_json.workspace = true
19+
serde_with.workspace = true
20+
tokio.workspace = true
21+
once_cell.workspace = true
22+
23+
[dependencies.sos-sdk]
24+
version = "0.14.5"
25+
path = "../sdk"
26+
27+
[build-dependencies]
28+
rustc_version = "0.4.0"

crates/account_extras/build.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use rustc_version::{version_meta, Channel};
2+
3+
fn main() {
4+
// Set cfg flags depending on release channel
5+
let channel = match version_meta().unwrap().channel {
6+
Channel::Stable => "CHANNEL_STABLE",
7+
Channel::Beta => "CHANNEL_BETA",
8+
Channel::Nightly => "CHANNEL_NIGHTLY",
9+
Channel::Dev => "CHANNEL_DEV",
10+
};
11+
println!("cargo:rustc-cfg={}", channel)
12+
}

crates/account_extras/src/error.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use thiserror::Error;
2+
3+
/// Error type for the account extras library.
4+
#[derive(Debug, Error)]
5+
pub enum Error {
6+
/// Error generated when a preference is not a number.
7+
#[cfg(feature = "preferences")]
8+
#[error("expecting integer preference for '{0}'")]
9+
PreferenceTypeNumber(String),
10+
11+
/// Error generated when a preference is not a boolean.
12+
#[cfg(feature = "preferences")]
13+
#[error("expecting boolean preference for '{0}'")]
14+
PreferenceTypeBool(String),
15+
16+
/// Error generated when a preference is not a string.
17+
#[cfg(feature = "preferences")]
18+
#[error("expecting string preference for '{0}'")]
19+
PreferenceTypeString(String),
20+
21+
/// Error generated when a preference is not a string list.
22+
#[cfg(feature = "preferences")]
23+
#[error("expecting string list preference for '{0}'")]
24+
PreferenceTypeStringList(String),
25+
26+
/// Error generated when a system message is not found.
27+
#[cfg(feature = "system-messages")]
28+
#[error("no system message for key '{0}'")]
29+
NoSysMessage(String),
30+
31+
/// Errors generated by the IO module.
32+
#[error(transparent)]
33+
Io(#[from] std::io::Error),
34+
35+
/// Errors generated by the JSON library.
36+
#[error(transparent)]
37+
Json(#[from] serde_json::Error),
38+
39+
/// Errors generated by the SDK library.
40+
#[error(transparent)]
41+
Sdk(#[from] sos_sdk::Error),
42+
}

crates/account_extras/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Extra functions for local accounts in the [SDK](https://docs.rs/sos-sdk) for [Save Our Secrets](https://saveoursecrets.com).
2+
#![deny(missing_docs)]
3+
#![forbid(unsafe_code)]
4+
#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
5+
6+
mod error;
7+
8+
/// Errors generated by the extras library.
9+
pub use error::Error;
10+
11+
/// Result type for the extras library.
12+
pub type Result<T> = std::result::Result<T, Error>;
13+
14+
#[cfg(feature = "preferences")]
15+
pub mod preferences;
16+
17+
#[cfg(feature = "security-report")]
18+
pub mod security_report;
19+
20+
#[cfg(feature = "system-messages")]
21+
pub mod system_messages;

crates/sdk/src/account/preferences.rs renamed to crates/account_extras/src/preferences.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,33 @@
44
//! of named keys to typed data similar to
55
//! the shared preferences provided by an operating
66
//! system library.
7-
use crate::{
8-
identity::PublicIdentity, signer::ecdsa::Address, vfs, Error, Paths,
9-
Result,
10-
};
7+
use crate::{Error, Result};
118
use once_cell::sync::Lazy;
129
use serde::{Deserialize, Serialize};
10+
use sos_sdk::{
11+
constants::JSON_EXT, identity::PublicIdentity, signer::ecdsa::Address,
12+
vfs, Paths,
13+
};
1314
use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
1415
use tokio::sync::Mutex;
1516

17+
/// File thats stores account-level preferences.
18+
pub const PREFERENCES_FILE: &str = "preferences";
19+
20+
/// Path to the file used to store account-level preferences.
21+
///
22+
/// # Panics
23+
///
24+
/// If this set of paths are global (no user identifier).
25+
pub fn preferences_path(paths: &Paths) -> PathBuf {
26+
if paths.is_global() {
27+
panic!("preferences are not accessible for global paths");
28+
}
29+
let mut vault_path = paths.user_dir().join(PREFERENCES_FILE);
30+
vault_path.set_extension(JSON_EXT);
31+
vault_path
32+
}
33+
1634
static CACHE: Lazy<Mutex<HashMap<Address, Arc<Mutex<Preferences>>>>> =
1735
Lazy::new(|| Mutex::new(HashMap::new()));
1836

@@ -55,7 +73,7 @@ impl CachedPreferences {
5573

5674
let mut cache = CACHE.lock().await;
5775
let paths = Paths::new(&data_dir, address.to_string());
58-
let file = paths.preferences();
76+
let file = preferences_path(&paths);
5977
let prefs = if vfs::try_exists(&file).await? {
6078
let mut prefs = Preferences::new(&paths);
6179
prefs.load().await?;
@@ -152,7 +170,7 @@ impl Preferences {
152170
///
153171
pub fn new(paths: &Paths) -> Self {
154172
Self {
155-
path: paths.preferences(),
173+
path: preferences_path(paths),
156174
values: Default::default(),
157175
}
158176
}

crates/sdk/src/account/security_report.rs renamed to crates/account_extras/src/security_report.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,100 @@
11
//! Types for security report generation.
2-
use crate::{
2+
use serde::{Deserialize, Serialize};
3+
use sos_sdk::{
4+
account::Account,
5+
hex,
36
vault::{
47
secret::{Secret, SecretId, SecretType},
58
Gatekeeper, Summary, VaultId,
69
},
710
zxcvbn::{Entropy, Score},
811
Result,
912
};
10-
use serde::{Deserialize, Serialize};
13+
14+
/// Generate a security report.
15+
pub async fn generate_security_report<A, E, T, D, R>(
16+
account: &A,
17+
options: SecurityReportOptions<T, D, R>,
18+
) -> std::result::Result<SecurityReport<T>, E>
19+
where
20+
A: Account,
21+
D: Fn(Vec<String>) -> R + Send + Sync,
22+
R: std::future::Future<Output = Vec<T>> + Send + Sync,
23+
E: From<A::Error> + From<sos_sdk::Error>,
24+
{
25+
let mut records = Vec::new();
26+
let mut hashes = Vec::new();
27+
let folders = account.list_folders().await?;
28+
let targets: Vec<Summary> = folders
29+
.into_iter()
30+
.filter(|folder| {
31+
if let Some(target) = &options.target {
32+
return folder.id() == &target.0;
33+
}
34+
!options.excludes.contains(folder.id())
35+
})
36+
.collect();
37+
38+
for target in targets {
39+
let storage = account.storage().await?;
40+
let reader = storage.read().await;
41+
42+
let folder = reader.cache().get(target.id()).unwrap();
43+
let keeper = folder.keeper();
44+
45+
let vault = keeper.vault();
46+
let mut password_hashes: Vec<(
47+
SecretId,
48+
(Option<Entropy>, Vec<u8>),
49+
Option<SecretId>,
50+
)> = Vec::new();
51+
52+
if let Some(target) = &options.target {
53+
secret_security_report(
54+
&target.1,
55+
keeper,
56+
&mut password_hashes,
57+
target.2.as_ref(),
58+
)
59+
.await?;
60+
} else {
61+
for secret_id in vault.keys() {
62+
secret_security_report(
63+
secret_id,
64+
keeper,
65+
&mut password_hashes,
66+
None,
67+
)
68+
.await?;
69+
}
70+
}
71+
72+
for (secret_id, check, field_id) in password_hashes {
73+
let (entropy, sha1) = check;
74+
75+
let record = SecurityReportRecord {
76+
folder: target.clone(),
77+
secret_id,
78+
field_id,
79+
entropy,
80+
};
81+
82+
hashes.push(hex::encode(sha1));
83+
records.push(record);
84+
}
85+
}
86+
87+
let database_checks =
88+
if let Some(database_handler) = options.database_handler {
89+
(database_handler)(hashes).await
90+
} else {
91+
vec![]
92+
};
93+
Ok(SecurityReport {
94+
records,
95+
database_checks,
96+
})
97+
}
1198

1299
/// Specific target for a security report.
13100
pub struct SecurityReportTarget(

crates/sdk/src/account/system_messages.rs renamed to crates/account_extras/src/system_messages.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,31 @@
1313
//! changes to the underlying collection. This allows
1414
//! an interface to show the number of unread system
1515
//! messages.
16-
use crate::{vfs, Error, Paths, Result};
16+
use crate::{Error, Result};
1717
use serde::{Deserialize, Serialize};
1818
use serde_with::{serde_as, DisplayFromStr};
19+
use sos_sdk::{
20+
constants::JSON_EXT, time::OffsetDateTime, urn::Urn, vfs, Paths,
21+
};
1922
use std::{cmp::Ordering, collections::HashMap, path::PathBuf};
20-
use time::OffsetDateTime;
2123
use tokio::sync::broadcast;
22-
use urn::Urn;
24+
25+
/// File thats stores account-level system messages.
26+
pub const SYSTEM_MESSAGES_FILE: &str = "system-messages";
27+
28+
/// Path to the file used to store account-level system messages.
29+
///
30+
/// # Panics
31+
///
32+
/// If this set of paths are global (no user identifier).
33+
pub fn system_messages_path(paths: &Paths) -> PathBuf {
34+
if paths.is_global() {
35+
panic!("system messages are not accessible for global paths");
36+
}
37+
let mut vault_path = paths.user_dir().join(SYSTEM_MESSAGES_FILE);
38+
vault_path.set_extension(JSON_EXT);
39+
vault_path
40+
}
2341

2442
/// System messages count.
2543
#[derive(Debug, Default, Clone, Eq, PartialEq)]
@@ -161,7 +179,7 @@ impl SystemMessages {
161179
///
162180
pub fn new(paths: &Paths) -> Self {
163181
Self {
164-
path: paths.system_messages(),
182+
path: system_messages_path(paths),
165183
messages: Default::default(),
166184
channel: stream_channel(),
167185
}

crates/integration_tests/tests/local_account/security_report.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::test_utils::{setup, teardown};
22
use anyhow::Result;
33
use secrecy::SecretString;
4-
use sos_net::sdk::{prelude::*, zxcvbn::Score};
4+
use sos_net::{
5+
extras::security_report::*,
6+
sdk::{prelude::*, zxcvbn::Score},
7+
};
58

69
#[tokio::test]
710
async fn local_security_report() -> Result<()> {
@@ -36,9 +39,14 @@ async fn local_security_report() -> Result<()> {
3639
target: None,
3740
};
3841

39-
let report = account
40-
.generate_security_report::<bool, _, _>(report_options)
41-
.await?;
42+
let report = generate_security_report::<
43+
LocalAccount,
44+
sos_net::sdk::Error,
45+
bool,
46+
_,
47+
_,
48+
>(&account, report_options)
49+
.await?;
4250

4351
let weak_record = report
4452
.records

0 commit comments

Comments
 (0)