Skip to content

Commit

Permalink
First sweettest for generate
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Apr 3, 2024
1 parent 3c4eec0 commit 216c72d
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 50 deletions.
11 changes: 11 additions & 0 deletions checked_cli/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 checked_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ rpassword = "7.3.1"

[dev-dependencies]
holochain = { version = "0.3.0-beta-dev.43", default-features = false, features = ["sweetest"] }
signing_keys_types = { path = "../types/signing_keys" }
55 changes: 45 additions & 10 deletions checked_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,24 @@ pub struct GenerateArgs {
#[arg(long, short, default_value_t = String::from("default"))]
pub name: String,

/// The admin port for Holochain
/// The admin port for Holochain.
#[arg(long, short)]
pub port: Option<u16>,

/// Provide a password on the command line instead of prompting for it on platforms
/// where a prompt isn't supported.
#[cfg(not(any(windows, unix)))]
pub password: String,
/// Provide a password on the command line instead of prompting for it.
///
/// If this flag is not provided, then an interactive prompt is used to get the password.
///
/// This is not recommended when using as a CLI flag because the password may stay in your
/// shell history. Use the interactive prompt instead if possible!
#[arg(long)]
pub password: Option<String>,

/// Whether to distribute the key on Holochain after generating it.
///
/// If this flag is not provided, then an interactive prompt is used to confirm.
#[arg(long, short)]
pub distribute: Option<bool>,

/// The directory to save the key in.
///
Expand All @@ -58,12 +68,16 @@ pub struct SignArgs {
#[arg(long, short, default_value_t = String::from("default"))]
pub name: String,

/// Provide a password on the command line instead of prompting for it on platforms
/// where a prompt isn't supported.
#[cfg(not(any(windows, unix)))]
pub password: String,
/// Provide a password on the command line instead of prompting for it.
///
/// If this flag is not provided, then an interactive prompt is used to get the password.
///
/// This is not recommended when using as a CLI flag because the password may stay in your
/// shell history. Use the interactive prompt instead if possible!
#[arg(long)]
pub password: Option<String>,

/// The directory to save the key in.
/// The directory to find the signing key in.
///
/// Defaults to `.config/checked` in your home directory.
#[arg(long, short)]
Expand Down Expand Up @@ -108,6 +122,21 @@ pub struct DistributeArgs {
/// Defaults to `default`.
#[arg(long, short, default_value_t = String::from("default"))]
pub name: String,

/// Provide a password on the command line instead of prompting for it.
///
/// If this flag is not provided, then an interactive prompt is used to get the password.
///
/// This is not recommended when using as a CLI flag because the password may stay in your
/// shell history. Use the interactive prompt instead if possible!
#[arg(long)]
pub password: Option<String>,

/// The directory to find the verification key in.
///
/// Defaults to `.config/checked` in your home directory.
#[arg(long, short)]
pub path: Option<PathBuf>,
}

#[derive(clap::Args)]
Expand All @@ -124,4 +153,10 @@ pub struct FetchArgs {
/// Defaults to `default`.
#[arg(long, short, default_value_t = String::from("default"))]
pub name: String,

/// The directory to find the signing key in.
///
/// Defaults to `.config/checked` in your home directory.
#[arg(long, short)]
pub path: Option<PathBuf>,
}
13 changes: 8 additions & 5 deletions checked_cli/src/distribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use checked_types::{DistributeVfKeyRequest, VerificationKeyType};
use crate::cli::DistributeArgs;
use crate::common::{get_store_dir, get_verification_key_path};
use crate::hc_client::{get_authenticated_app_agent_client, maybe_handle_holochain_error};
use crate::password::GetPassword;
use crate::prelude::SignArgs;
use crate::sign::sign;

Expand Down Expand Up @@ -57,10 +58,11 @@ const PROOF_WORDS: [&str; 40] = [
pub async fn distribute(distribute_args: DistributeArgs) -> anyhow::Result<()> {
println!("Distributing key: {}", distribute_args.name);

let mut app_client = get_authenticated_app_agent_client(distribute_args.port).await?;
let mut app_client =
get_authenticated_app_agent_client(distribute_args.port, distribute_args.path.clone())
.await?;

// TODO path arg
let store_dir = get_store_dir(None)?;
let store_dir = get_store_dir(distribute_args.path.clone())?;
let vk_path = get_verification_key_path(&store_dir, &distribute_args.name);

let proof = generate_proof();
Expand All @@ -76,7 +78,8 @@ pub async fn distribute(distribute_args: DistributeArgs) -> anyhow::Result<()> {

let sig_path = sign(SignArgs {
name: distribute_args.name.clone(),
path: None,
password: Some(distribute_args.get_password()?),
path: distribute_args.path.clone(),
file: tmp_file.path().to_path_buf(),
output: None,
})?;
Expand All @@ -99,7 +102,7 @@ pub async fn distribute(distribute_args: DistributeArgs) -> anyhow::Result<()> {
)
.await
.map_err(|e| {
maybe_handle_holochain_error(&e);
maybe_handle_holochain_error(&e, distribute_args.path);
anyhow::anyhow!("Failed to get signatures for the asset: {:?}", e)
})?;

Expand Down
4 changes: 3 additions & 1 deletion checked_cli/src/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct FetchState {
}

pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result<()> {
let mut app_client = hc_client::get_authenticated_app_agent_client(fetch_args.port).await?;
let mut app_client =
hc_client::get_authenticated_app_agent_client(fetch_args.port, fetch_args.path).await?;

// TODO if this fails because the credentials are no longer valid then we need a recovery mechanism that isn't `rm ~/.checked/credentials.json`
let response = app_client
Expand Down Expand Up @@ -131,6 +132,7 @@ pub async fn fetch(fetch_args: FetchArgs) -> anyhow::Result<()> {

let signature_path = sign(SignArgs {
name: fetch_args.name.clone(),
password: None,
path: None,
file: PathBuf::from(file),
output: None,
Expand Down
23 changes: 12 additions & 11 deletions checked_cli/src/generate.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::cli::{DistributeArgs, GenerateArgs};
use crate::common::{get_signing_key_path, get_store_dir, get_verification_key_path, open_file};
use crate::distribute::distribute;
use crate::password::GetPassword;
use minisign::KeyPair;
use std::io::Write;

pub async fn generate(generate_args: GenerateArgs) -> anyhow::Result<()> {
let store_dir = get_store_dir(generate_args.path)?;
let store_dir = get_store_dir(generate_args.path.clone())?;

// Signing key
let sk_path = get_signing_key_path(&store_dir, &generate_args.name);
Expand All @@ -15,16 +16,13 @@ pub async fn generate(generate_args: GenerateArgs) -> anyhow::Result<()> {
let vk_path = get_verification_key_path(&store_dir, &generate_args.name);
let mut vk_file = open_file(&vk_path)?;

#[cfg(not(any(windows, unix)))]
let password = generate_args.password;
#[cfg(any(windows, unix))]
let password = rpassword::prompt_password("New password: ")?;
let password = generate_args.get_password()?;

let _pk = KeyPair::generate_and_write_encrypted_keypair(
&mut vk_file,
&mut sk_file,
None,
Some(password),
Some(password.clone()),
)?
.pk;

Expand All @@ -39,12 +37,13 @@ pub async fn generate(generate_args: GenerateArgs) -> anyhow::Result<()> {
"The public key was saved as {} - That one can be public.\n",
vk_path.display()
);
// println!("Files signed using this key can be verified with the following command:\n");
// println!("checked verify <file> -P {}", _pk.to_base64());

let should_distribute = dialoguer::Confirm::new()
.with_prompt("Would you like to distribute this key on Holochain?")
.interact()?;
let should_distribute = match generate_args.distribute {
Some(distribute) => distribute,
None => dialoguer::Confirm::new()
.with_prompt("Would you like to distribute this key on Holochain?")
.interact()?,
};

if !should_distribute {
return Ok(());
Expand All @@ -60,6 +59,8 @@ pub async fn generate(generate_args: GenerateArgs) -> anyhow::Result<()> {
distribute(DistributeArgs {
port: admin_port,
name: generate_args.name,
password: Some(password),
path: generate_args.path,
})
.await?;

Expand Down
30 changes: 19 additions & 11 deletions checked_cli/src/hc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ use holochain_types::websocket::AllowedOrigins;
use serde::{Deserialize, Serialize};
use std::fs::{File, Permissions};
use std::io::Write;
use std::path::PathBuf;

const DEFAULT_INSTALLED_APP_ID: &str = "checked";

pub async fn get_authenticated_app_agent_client(
admin_port: u16,
path: Option<PathBuf>,
) -> anyhow::Result<AppAgentWebsocket> {
// TODO connect timeout not configurable! Really slow if Holochain is not running.
let mut admin_client = AdminWebsocket::connect(format!("localhost:{admin_port}")).await?;

let mut signer = ClientAgentSigner::new();
load_or_create_signing_credentials(&mut admin_client, &mut signer).await?;
load_or_create_signing_credentials(&mut admin_client, &mut signer, path).await?;

let app_port = find_or_create_app_interface(&mut admin_client).await?;

Expand All @@ -31,12 +33,15 @@ pub async fn get_authenticated_app_agent_client(
.await
}

pub fn maybe_handle_holochain_error(conductor_api_error: &ConductorApiError) {
pub fn maybe_handle_holochain_error(
conductor_api_error: &ConductorApiError,
path: Option<PathBuf>,
) {
match conductor_api_error {
// TODO brittle, would be nice if the errors for some important failures were more specific.
ConductorApiError::SignZomeCallError(e) if e == "Provenance not found" => {
eprintln!("Saved credentials for Holochain appear invalid, removing them. Please re-run this command");
if let Ok(e) = get_credentials_path() {
if let Ok(e) = get_credentials_path(path) {
if std::fs::remove_file(e).is_ok() {
println!("Successfully removed credentials");
return;
Expand Down Expand Up @@ -71,14 +76,15 @@ async fn find_or_create_app_interface(admin_client: &mut AdminWebsocket) -> anyh
async fn load_or_create_signing_credentials(
admin_client: &mut AdminWebsocket,
signer: &mut ClientAgentSigner,
path: Option<PathBuf>,
) -> anyhow::Result<()> {
match try_load_credentials()? {
match try_load_credentials(path.clone())? {
Some((cell_id, credentials)) => {
signer.add_credentials(cell_id, credentials);
}
None => {
let (cell_id, credentials) = create_new_credentials(admin_client).await?;
dump_credentials(cell_id.clone(), &credentials)?;
dump_credentials(cell_id.clone(), &credentials, path)?;
signer.add_credentials(cell_id, credentials);
}
}
Expand Down Expand Up @@ -136,6 +142,7 @@ struct SavedCredentials {
fn dump_credentials(
cell_id: CellId,
signing_credentials: &SigningCredentials,
path: Option<PathBuf>,
) -> anyhow::Result<()> {
let saved = SavedCredentials {
cell_id: cell_id.clone(),
Expand All @@ -147,8 +154,7 @@ fn dump_credentials(
let serialized = serde_json::to_string(&saved)
.map_err(|e| anyhow::anyhow!("Error serializing credentials: {:?}", e))?;

// generate_args.path
let credentials_path = get_credentials_path()?;
let credentials_path = get_credentials_path(path)?;

let mut f = File::options()
.create(true)
Expand All @@ -171,8 +177,10 @@ fn dump_credentials(
Ok(())
}

fn try_load_credentials() -> anyhow::Result<Option<(CellId, SigningCredentials)>> {
let credentials_path = get_credentials_path()?;
fn try_load_credentials(
path: Option<PathBuf>,
) -> anyhow::Result<Option<(CellId, SigningCredentials)>> {
let credentials_path = get_credentials_path(path)?;

let f = match File::open(credentials_path) {
Ok(f) => f,
Expand Down Expand Up @@ -212,6 +220,6 @@ fn try_load_credentials() -> anyhow::Result<Option<(CellId, SigningCredentials)>
)))
}

fn get_credentials_path() -> anyhow::Result<std::path::PathBuf> {
Ok(get_store_dir(None)?.join("credentials.json"))
fn get_credentials_path(path: Option<PathBuf>) -> anyhow::Result<std::path::PathBuf> {
Ok(get_store_dir(path)?.join("credentials.json"))
}
37 changes: 25 additions & 12 deletions checked_cli/src/password.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
use crate::cli::{GenerateArgs, SignArgs};
use crate::cli::{DistributeArgs, GenerateArgs, SignArgs};

pub trait GetPassword {
fn get_password(&self) -> anyhow::Result<String>;
}

impl GetPassword for GenerateArgs {
fn get_password(&self) -> anyhow::Result<String> {
#[cfg(not(any(windows, unix)))]
return Ok(self.password);
#[cfg(any(windows, unix))]
return Ok(rpassword::prompt_password("New password: ")?);
get_password_common(self.password.as_ref(), "New password: ")
}
}

impl GetPassword for SignArgs {
fn get_password(&self) -> anyhow::Result<String> {
#[cfg(not(any(windows, unix)))]
return Ok(self.password);
#[cfg(any(windows, unix))]
return Ok(rpassword::prompt_password(format!(
"Password for '{}': ",
self.name
))?);
get_password_common(
self.password.as_ref(),
format!("Password for '{}': ", self.name),
)
}
}

impl GetPassword for DistributeArgs {
fn get_password(&self) -> anyhow::Result<String> {
get_password_common(
self.password.as_ref(),
format!("Password for '{}': ", self.name),
)
}
}

fn get_password_common(
maybe_password: Option<&String>,
prompt: impl ToString,
) -> anyhow::Result<String> {
match maybe_password {
Some(password) => Ok(password.clone()),
None => Ok(rpassword::prompt_password(prompt)?),
}
}
Loading

0 comments on commit 216c72d

Please sign in to comment.