Skip to content

Commit

Permalink
Cli shared recovery create - ask for threshold if not provided. (#9127)
Browse files Browse the repository at this point in the history
* Cli shared recovery create - ask for threshold if not provided.

* CLI shared recovery create - add test for interactive threshold

* [CLI] rework spinners and display for shared recovery.
  • Loading branch information
AureliaDolo committed Dec 16, 2024
1 parent 75e6950 commit 818f764
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 78 deletions.
1 change: 1 addition & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ Reqwest
rerunfailures
reseted
retrier
rexpect
Richcmp
Rlib
rmvb
Expand Down
78 changes: 76 additions & 2 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ crypto_secretbox = { version = "0.1.1", default-features = false }
ctrlc = { version = "3.4.5", default-features = false }
# You need base64&co ? This is the crate you need !
data-encoding = { version = "2.6.0", default-features = false }
dialoguer = { version = "0.11.0", default-features = false }
digest = { version = "0.10.7", default-features = false }
dirs = { version = "5.0.1", default-features = false }
ed25519-dalek = { version = "2.1.1", default-features = false }
Expand Down Expand Up @@ -172,6 +173,7 @@ regex = { version = "1.11.1", default-features = false }
regex-syntax = { version = "0.8.4", default-features = false }
reqwest = { version = "0.12.9", default-features = false }
reqwest-eventsource = { version = "0.6.0", default-features = false }
rexpect = "0.6.0"
rmp-serde = { version = "1.3.0", default-features = false }
rpassword = { version = "7.3.1", default-features = false }
rsa = { version = "0.8.2", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ libparsec_platform_ipc = { workspace = true }

anyhow = { workspace = true }
clap = { workspace = true, features = ["default", "derive", "env"] }
dialoguer = { workspace = true }
env_logger = { workspace = true, features = ["auto-color", "humantime", "regex"] }
log = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
Expand All @@ -45,6 +46,7 @@ libparsec = { workspace = true, features = ["cli-tests"] }

assert_cmd = { workspace = true }
predicates = { workspace = true, features = ["regex"] }
rexpect = { workspace = true }
rstest = { workspace = true }
uuid = { workspace = true, features = ["v6", "std", "rng"] }

Expand Down
27 changes: 22 additions & 5 deletions cli/src/commands/shared_recovery/create.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{collections::HashMap, num::NonZeroU8};

use dialoguer::Input;
use libparsec::{UserID, UserProfile};

use crate::utils::{start_spinner, StartedClient};
use crate::utils::{start_spinner, StartedClient, GREEN_CHECKMARK};

// TODO: should provide the recipients and their share count as a single parameter
// e.g. `[email protected]=2,[email protected]=3`
Expand All @@ -20,8 +21,8 @@ crate::clap_parser_with_shared_opts_builder!(
#[arg(short, long, requires = "recipients", num_args = 1..=255)]
weights: Option<Vec<NonZeroU8>>,
/// Threshold number of shares required to proceed with recovery.
#[arg(short, long)]
threshold: NonZeroU8,
#[arg(short, long, requires = "recipients")]
threshold: Option<NonZeroU8>,
}
);

Expand All @@ -36,8 +37,9 @@ pub async fn create_shared_recovery(args: Args, client: &StartedClient) -> anyho
} = args;

{
let _spinner = start_spinner("Poll server for new certificates".into());
let mut spinner = start_spinner("Poll server for new certificates".into());
client.poll_server_for_new_certificates().await?;
spinner.stop_with_symbol(GREEN_CHECKMARK);
}

let mut handle = start_spinner("Creating shared recovery setup".into());
Expand Down Expand Up @@ -81,12 +83,27 @@ pub async fn create_shared_recovery(args: Args, client: &StartedClient) -> anyho
.map(|user_id| (user_id, weight))
.collect()
};
// we must stop the handle here to avoid messing up with the threshold choice
handle.stop_with_newline();
let threshold = if let Some(t) = threshold {
t
} else {
// note that this is a blocking call
Input::<NonZeroU8>::new()
.with_prompt(format!(
"Choose a threshold between 1 and {}\nThe threshold is the minimum number of recipients that one must gather to recover the account",
per_recipient_shares.len()
)) .interact_text()?
};
let mut handle = start_spinner("Creating shared recovery setup".into());

client
.setup_shamir_recovery(per_recipient_shares, threshold)
.await?;

handle.stop_with_message("Shared recovery setup has been created".into());
handle.stop_with_message(format!(
"{GREEN_CHECKMARK} Shared recovery setup has been created"
));

client.stop().await;

Expand Down
8 changes: 6 additions & 2 deletions cli/src/commands/shared_recovery/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
let client = load_client(&config_dir, device, password_stdin).await?;

{
let _spinner = start_spinner("Poll server for new certificates".into());
let mut spinner = start_spinner("Poll server for new certificates".into());
client.poll_server_for_new_certificates().await?;
spinner.stop_with_symbol(GREEN_CHECKMARK);
}
let mut handle = start_spinner("Deleting shared recovery setup".into());

client.delete_shamir_recovery().await?;

println!("Deleted shared recovery for {}", client.user_id());
handle.stop_with_message(format!(
"{GREEN_CHECKMARK} Shared recovery setup has been deleted"
));

client.stop().await;

Expand Down
11 changes: 7 additions & 4 deletions cli/src/commands/shared_recovery/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
let client = load_client(&config_dir, device, password_stdin).await?;

{
let _spinner = start_spinner("Poll server for new certificates".into());
let mut spinner = start_spinner("Poll server for new certificates".into());
client.poll_server_for_new_certificates().await?;
spinner.stop_with_symbol(GREEN_CHECKMARK);
}

let info = client.get_self_shamir_recovery().await?;
Expand All @@ -33,15 +34,17 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
..
} => println!("{RED}Deleted{RESET} shared recovery - deleted by {deleted_by} on {deleted_on}"),
libparsec_client::SelfShamirRecoveryInfo::SetupAllValid {
..
} => println!("Shared recovery {GREEN}set up{RESET}"),
per_recipient_shares, threshold, ..
} => println!("Shared recovery {GREEN}set up{RESET} with threshold {threshold}\n{}", per_recipient_shares.iter().map(|(recipient, share)| {
format!("• User {recipient} has {share} share(s)") // TODO: special case if there is only on share
}).join("\n")),
libparsec_client::SelfShamirRecoveryInfo::SetupWithRevokedRecipients {
threshold,
per_recipient_shares,
revoked_recipients,
..
} => println!(
"Shared recovery for {YELLOW}set up{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
"Shared recovery {YELLOW}set up{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
revoked = revoked_recipients.iter().join(", "),
revoked_len = revoked_recipients.len(),
total = per_recipient_shares.len()),
Expand Down
15 changes: 9 additions & 6 deletions cli/src/commands/shared_recovery/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
let client = load_client(&config_dir, device, password_stdin).await?;

{
let _spinner = start_spinner("Poll server for new certificates".into());
let mut spinner = start_spinner("Poll server for new certificates".into());
client.poll_server_for_new_certificates().await?;
spinner.stop_with_symbol(GREEN_CHECKMARK);
}

let res = client.list_shamir_recoveries_for_others().await?;
Expand All @@ -38,16 +39,18 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
deleted_on,
deleted_by,
..
} => println!("Deleted shared recovery for {RED}{user_id}{RESET} - deleted by {deleted_by} on {deleted_on}"),
} => println!("Deleted shared recovery for {RED}{user_id}{RESET} - deleted by {deleted_by} on {deleted_on}"),
libparsec_client::OtherShamirRecoveryInfo::SetupAllValid {
user_id,..
} => println!("Shared recovery for {GREEN}{user_id}{RESET}"),
user_id, threshold, per_recipient_shares,..
} => println!("Shared recovery for {GREEN}{user_id}{RESET} with threshold {threshold}\n{}", per_recipient_shares.iter().map(|(recipient, share)| {
format!("\t• User {recipient} has {share} share(s)") // TODO: special case if there is only on share
}).join("\n")),
libparsec_client::OtherShamirRecoveryInfo::SetupWithRevokedRecipients {
user_id,
threshold,
per_recipient_shares,
revoked_recipients,..
} => println!("Shared recovery for {YELLOW}{user_id}{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
} => println!("Shared recovery for {YELLOW}{user_id}{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
revoked = revoked_recipients.iter().join(", "),
revoked_len = revoked_recipients.len(),
total = per_recipient_shares.len()),
Expand All @@ -56,7 +59,7 @@ pub async fn main(args: Args) -> anyhow::Result<()> {
threshold,
per_recipient_shares,
revoked_recipients,..
} => println!("Unusable shared recovery for {RED}{user_id}{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
} => println!("Unusable shared recovery for {RED}{user_id}{RESET} - contains revoked recipients: {revoked} ({revoked_len} out of {total} total recipients, with threshold {threshold})",
revoked = revoked_recipients.iter().join(", "),
revoked_len = revoked_recipients.len(),
total = per_recipient_shares.len()),
Expand Down
1 change: 1 addition & 0 deletions cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub const GREEN: &str = "\x1B[92m";
pub const RED: &str = "\x1B[91m";
pub const RESET: &str = "\x1B[39m";
pub const YELLOW: &str = "\x1B[33m";
pub const GREEN_CHECKMARK: &str = "\x1B[92m🗸\x1B[39m";

pub fn format_devices(
devices: &[AvailableDevice],
Expand Down
Loading

0 comments on commit 818f764

Please sign in to comment.