diff --git a/iota-cli/src/elements/console_card.rs b/iota-cli/src/elements/console_card.rs index f7b0c8f..a394e96 100644 --- a/iota-cli/src/elements/console_card.rs +++ b/iota-cli/src/elements/console_card.rs @@ -428,7 +428,7 @@ pub async fn run_command(command: &str) { { log!("Created user {}", user.user_id); } else { - log!("Failed to create user"); + log!("User creation: Failed to create user. See errors above."); } } ["user", "remove", username] => { @@ -436,7 +436,7 @@ pub async fn run_command(command: &str) { user_manager::remove_user(user.user_id); log!("Removed user {}", user.user_id); } else { - log!("Failed to find user"); + log!("User removal: Username doesn't exist"); } } ["user", "list"] => { @@ -457,7 +457,7 @@ pub async fn run_command(command: &str) { user_manager::remove_user(user.user_id); log!("Removed user {}", user.user_id); } else { - log!("Failed to find user"); + log!("User info: Username doesn't exist"); } } ["reload"] | ["restart"] => { diff --git a/omikron-connector/src/user_ops.rs b/omikron-connector/src/user_ops.rs index 1c34670..eb14bcc 100644 --- a/omikron-connector/src/user_ops.rs +++ b/omikron-connector/src/user_ops.rs @@ -1,6 +1,6 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; use hex; -use iota_logger::{PrintType, log, log_cv}; +use iota_logger::{PrintType, log, log_cv, log_t}; use iota_state::{RELOAD, SHUTDOWN}; use iota_storage::users::user_manager::{add_user, save_users}; use iota_storage::users::user_profile::UserProfile; @@ -15,27 +15,36 @@ use x448::{PublicKey, Secret}; use crate::omikron_connection::OMIKRON_CONNECTION; pub async fn create_user(username: &str) -> (Option, Option) { - let register_cv = CommunicationValue::new(CommunicationType::get_register); + let register_communication_value = CommunicationValue::new(CommunicationType::get_register); - let conn = OMIKRON_CONNECTION.clone(); + let connection = OMIKRON_CONNECTION.clone(); - let response_cv = match conn - .await_response(®ister_cv, Some(Duration::from_secs(20))) + let response_communication_value = match connection + .await_response(®ister_communication_value, Some(Duration::from_secs(20))) .await { - Ok(cv) => cv, - Err(_) => return (None, None), + Ok(communication_value) => communication_value, + Err(e) => { + log_t!("User creation: {}", e); + return (None, None); + } }; - log_cv!(PrintType::Omega, response_cv); + log_cv!(PrintType::Omega, response_communication_value); - let user_id = match response_cv.get_data(DataTypes::user_id).as_number() { + let user_id = match response_communication_value + .get_data(DataTypes::user_id) + .as_number() + { Some(id) => id, - None => return (None, None), + None => { + log_t!("User creation: Response returned none"); + return (None, None); + } }; - let mut buf = [0u8; 56]; + let mut buffer = [0u8; 56]; let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let private_key = Secret::from_bytes(&buf).unwrap(); + rng.fill_bytes(&mut buffer); + let private_key = Secret::from_bytes(&buffer).unwrap(); let public_key = PublicKey::from(&private_key); let mut hasher = Sha256::new(); @@ -47,7 +56,7 @@ pub async fn create_user(username: &str) -> (Option, Option OsRng.fill_bytes(&mut bytes); let reset_token = STANDARD.encode(&bytes); - let up = UserProfile::new( + let user_profile = UserProfile::new( user_id, username.to_string(), None, @@ -56,7 +65,7 @@ pub async fn create_user(username: &str) -> (Option, Option reset_token.clone(), ); - let cv = CommunicationValue::new(CommunicationType::complete_register_user) + let communication_value = CommunicationValue::new(CommunicationType::complete_register_user) .add_data(DataTypes::user_id, DataValue::Number(user_id)) .add_data(DataTypes::username, DataValue::Str(username.to_string())) .add_data( @@ -66,16 +75,17 @@ pub async fn create_user(username: &str) -> (Option, Option .add_data(DataTypes::iota_id, DataValue::Number(user_id)) .add_data(DataTypes::reset_token, DataValue::Str(reset_token)); - let response_cv = conn - .await_response(&cv, Some(Duration::from_secs(20))) + let response_communication_value = connection + .await_response(&communication_value, Some(Duration::from_secs(20))) .await; - if let Ok(resp) = response_cv { - log_cv!(PrintType::Omega, resp); - if !resp.is_type(CommunicationType::success) { + if let Ok(response) = response_communication_value { + log_cv!(PrintType::Omega, response); + if !response.is_type(CommunicationType::success) { return (None, None); } } else { + log_t!("User creation: Response returned none"); return (None, None); } *SHUTDOWN.write().await = true; @@ -87,7 +97,10 @@ pub async fn create_user(username: &str) -> (Option, Option &format!("{}::{}", user_id, STANDARD.encode(&private_key.as_bytes())), ); - add_user(up.clone()); + add_user(user_profile.clone()); save_users(); - (Some(up), Some(STANDARD.encode(&private_key.as_bytes()))) + ( + Some(user_profile), + Some(STANDARD.encode(&private_key.as_bytes())), + ) } diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..9f676a4 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/util/auto_update.rs b/src/util/auto_update.rs new file mode 100644 index 0000000..090c5a8 --- /dev/null +++ b/src/util/auto_update.rs @@ -0,0 +1,135 @@ +/* This file is used for the auto update function for the Iota. + * It connects to the git server from methanium and checks if + * the version has updated inside the cargo.toml file.*/ + +use anyhow::{Context, Result, anyhow}; +use semver::Version; +use serde::Deserialize; +use std::fs::File; +use std::io::copy; +use tempfile::NamedTempFile; + +use crate::log; // For logging messages into the iota + +const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const API_BASE: &str = "https://git.methanium.net/api/v1"; +const OWNER: &str = "Tensamin"; +const REPO: &str = "Iota"; + +#[derive(Debug, Deserialize)] +struct Release { + tag_name: String, + assets: Vec, +} + +#[derive(Debug, Deserialize)] +struct Asset { + name: String, + browser_download_url: String, +} + +async fn latest_release() -> Result { + let url = format!("{API_BASE}/repos/{OWNER}/{REPO}/releases/latest"); + + let response = reqwest::get(&url) + .await + .context("failed to query latest release.")?; + + if !response.status().is_success() { + return Err(anyhow!("release API returned {}", response.status())); + } + + Ok(response + .json() + .await + .context("failed to parse release JSON")?) +} + +async fn parse_tag_version(tag: &str) -> Result { + let normalized = tag.strip_prefix('v').unwrap_or(tag); + Ok(Version::parse(normalized)?) +} + +async fn current_version() -> Result { + Ok(Version::parse(CURRENT_VERSION)?) +} + +async fn asset_name_for_current_platform() -> String { + let os = std::env::consts::OS; + let arch = std::env::consts::ARCH; + + match (os, arch) { + ("linux", "x86_64") => "iota-linux-x86_64".to_string(), + ("linux", "aarch64") => "iota-linux-aarch64".to_string(), + ("windows", "x86_64") => "iota-windows-x86_64.exe".to_string(), + ("macos", "x86_64") => "iota-macos-x86_64".to_string(), + ("macos", "aarch64") => "iota-macos-aarch64".to_string(), + _ => panic!("unsupported platform: {os}/{arch}"), + } +} + +async fn download_asset(url: &str) -> Result { + let mut response = reqwest::get(url) + .await + .context("failed to download asset")?; + + if !response.status().is_success() { + return Err(anyhow!("asset download returned {}", response.status())); + } + + let tmp = NamedTempFile::new().context("failed to create temp file")?; + let mut out = File::create(tmp.path()).context("failed to open temp file")?; + + let bytes = response + .bytes() + .await + .context("failed to read response bytes")?; + + std::fs::write(tmp.path(), &bytes).context("failed to write file")?; + + Ok(tmp) +} + +async fn check_for_update() -> Result> { + let current = current_version().await?; + let release = latest_release().await?; + let latest = parse_tag_version(&release.tag_name).await?; + + if latest > current { + Ok(Some(release)) + } else { + Ok(None) + } +} + +async fn perform_update() -> Result { + let Some(release) = check_for_update().await? else { + return Ok(false); + }; + + let wanted_asset = asset_name_for_current_platform().await; + + let asset = release + .assets + .iter() + .find(|a| a.name == wanted_asset) + .ok_or_else(|| anyhow!("no matching asset found: {}", wanted_asset))?; + + log!("Downloading update: {}", asset.name); + + let downloaded = download_asset(&asset.browser_download_url).await?; + + self_replace::self_replace(downloaded.path()) + .context("failed to replace current executable")?; + + Ok(true) +} + +pub async fn check_update() -> Result { + if perform_update().await? { + return Ok(true); + } else { + return Ok(false); + } +}