diff options
Diffstat (limited to 'crates/turtle/src/command/client/account')
5 files changed, 0 insertions, 373 deletions
diff --git a/crates/turtle/src/command/client/account/change_password.rs b/crates/turtle/src/command/client/account/change_password.rs deleted file mode 100644 index b23f518d..00000000 --- a/crates/turtle/src/command/client/account/change_password.rs +++ /dev/null @@ -1,55 +0,0 @@ -use clap::Parser; -use eyre::{Result, bail}; - -use crate::atuin_client::{auth, settings::Settings}; -use rpassword::prompt_password; - -#[derive(Parser, Debug)] -pub(crate) struct Cmd { - #[clap(long, short)] - pub(crate) current_password: Option<String>, - - #[clap(long, short)] - pub(crate) new_password: Option<String>, - - /// The two-factor authentication code for your account, if any - #[clap(long, short)] - pub(crate) totp_code: Option<String>, -} - -impl Cmd { - pub(crate) async fn run(&self, settings: &Settings) -> Result<()> { - if !settings.logged_in().await? { - bail!("You are not logged in"); - } - - let client = auth::auth_client(settings).await; - - let current_password = self.current_password.clone().unwrap_or_else(|| { - prompt_password("Please enter the current password: ") - .expect("Failed to read from input") - }); - - if current_password.is_empty() { - bail!("please provide the current password"); - } - - let new_password = self.new_password.clone().unwrap_or_else(|| { - prompt_password("Please enter the new password: ").expect("Failed to read from input") - }); - - if new_password.is_empty() { - bail!("please provide a new password"); - } - - let totp_code = self.totp_code.clone(); - - client - .change_password(¤t_password, &new_password, totp_code.as_deref()) - .await?; - - println!("Account password successfully changed!"); - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/account/delete.rs b/crates/turtle/src/command/client/account/delete.rs deleted file mode 100644 index 722c39ec..00000000 --- a/crates/turtle/src/command/client/account/delete.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::atuin_client::{auth, settings::Settings}; -use clap::Parser; -use eyre::{Result, bail}; - -use super::login::read_user_password; - -#[derive(Parser, Debug)] -pub(crate) struct Cmd { - #[clap(long, short)] - pub(crate) password: Option<String>, - - /// The two-factor authentication code for your account, if any - #[clap(long, short)] - pub(crate) totp_code: Option<String>, -} - -impl Cmd { - pub(crate) async fn run(&self, settings: &Settings) -> Result<()> { - if !settings.logged_in().await? { - bail!("You are not logged in"); - } - - let client = auth::auth_client(settings).await; - - let password = self.password.clone().unwrap_or_else(read_user_password); - - if password.is_empty() { - bail!("please provide your password"); - } - - let mut totp_code = self.totp_code.clone(); - - client - .delete_account(&password, totp_code.as_deref()) - .await?; - - // Clean up sessions from meta store - let meta = Settings::meta_store().await?; - meta.delete_session().await?; - - println!("Your account is deleted"); - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/account/login.rs b/crates/turtle/src/command/client/account/login.rs deleted file mode 100644 index e9513879..00000000 --- a/crates/turtle/src/command/client/account/login.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::{io, path::PathBuf}; - -use clap::Parser; -use eyre::{Context, Result, bail}; -use tokio::{fs::File, io::AsyncWriteExt}; - -use crate::atuin_client::{ - auth, - encryption::{decode_key, load_key}, - record::sqlite_store::SqliteStore, - record::store::Store, - record::sync::{self, SyncError}, - settings::{Settings, SyncAuth}, -}; -use rpassword::prompt_password; - -#[derive(Parser, Debug)] -pub(crate) struct Cmd { - #[clap(long, short)] - pub(crate) username: Option<String>, - - #[clap(long, short)] - pub(crate) password: Option<String>, - - /// The encryption key for your account - #[clap(long, short)] - pub(crate) key: Option<String>, - - /// The two-factor authentication code for your account, if any - #[clap(long, short)] - pub(crate) totp_code: Option<String>, - - #[clap(long, hide = true)] - pub(crate) from_registration: bool, -} - -fn get_input() -> Result<String> { - let mut input = String::new(); - io::stdin().read_line(&mut input)?; - Ok(input.trim_end_matches(&['\r', '\n'][..]).to_string()) -} - -impl Cmd { - pub(crate) async fn run(&self, settings: &Settings, store: &SqliteStore) -> Result<()> { - match settings.resolve_sync_auth().await { - SyncAuth::Legacy { .. } => { - println!("You are logged in to your sync server."); - println!("Run 'atuin logout' to log out."); - return Ok(()); - } - SyncAuth::NotLoggedIn { .. } => {} - } - - self.run_legacy_login(settings, store).await?; - - verify_key_against_remote(settings).await - } - - /// Legacy login: always prompt for username/password interactively - /// (or accept them via flags). - async fn run_legacy_login(&self, settings: &Settings, store: &SqliteStore) -> Result<()> { - let username = or_user_input(self.username.clone(), "username"); - let password = self.password.clone().unwrap_or_else(read_user_password); - - self.prompt_and_store_key(settings, store).await?; - - let client = auth::auth_client(settings).await; - let response = client.login(&username, &password).await?; - - Settings::meta_store() - .await? - .save_session(&response.session) - .await?; - - println!("Logged in!"); - Ok(()) - } - - async fn prompt_and_store_key(&self, settings: &Settings, store: &SqliteStore) -> Result<()> { - let key_path = settings.key_path.as_str(); - let key_path = PathBuf::from(key_path); - - println!("IMPORTANT"); - println!( - "If you are already logged in on another machine, you must ensure that the key you use here is the same as the key you used there." - ); - println!("You can find your key by running 'atuin key' on the other machine."); - println!("Do not share this key with anyone."); - println!("\nRead more here: https://docs.atuin.sh/guide/sync/#login \n"); - - let key = or_user_input( - self.key.clone(), - "encryption key [blank to use existing key file]", - ); - - if key.is_empty() { - if key_path.exists() { - let bytes = fs_err::read_to_string(&key_path).context(format!( - "Existing key file at '{}' could not be read", - key_path.to_string_lossy() - ))?; - if decode_key(bytes).is_err() { - bail!(format!( - "The key in existing key file at '{}' is invalid", - key_path.to_string_lossy() - )); - } - } else { - panic!( - "No key provided and no existing key file found. Please use 'atuin key' on your other machine, or recover your key from a backup" - ) - } - } else if !key_path.exists() { - if decode_key(key.clone()).is_err() { - bail!("The specified key is invalid"); - } - - let mut file = File::create(&key_path).await?; - file.write_all(key.as_bytes()).await?; - } else { - // we now know that the user has logged in specifying a key, AND that the key path - // exists - - // 1. check if the saved key and the provided key match. if so, nothing to do. - // 2. if not, re-encrypt the local history and overwrite the key - let current_key: [u8; 32] = load_key(settings)?.into(); - - let encoded = key.clone(); // gonna want to save it in a bit - let new_key: [u8; 32] = decode_key(key) - .context("Could not decode provided key; is not valid base64-encoded key")? - .into(); - - if new_key != current_key { - println!("\nRe-encrypting local store with new key"); - - store.re_encrypt(¤t_key, &new_key).await?; - - println!("Writing new key"); - let mut file = File::create(&key_path).await?; - file.write_all(encoded.as_bytes()).await?; - } - } - - Ok(()) - } -} - -async fn verify_key_against_remote(settings: &Settings) -> Result<()> { - let key: [u8; 32] = load_key(settings) - .context("could not load encryption key for verification")? - .into(); - - let client = sync::build_client(settings).await?; - let remote_index = match client.record_status().await { - Ok(idx) => idx, - Err(e) => { - tracing::warn!("could not fetch remote status to verify key: {e}"); - return Ok(()); - } - }; - - match sync::check_encryption_key(&client, &remote_index, &key).await { - Ok(()) => Ok(()), - Err(SyncError::WrongKey) => { - // Roll back the saved session so the user is not left in a - // half-authenticated state with a key that can't read the data. - if let Ok(meta) = Settings::meta_store().await { - let _ = meta.delete_session().await; - } - crate::print_error::print_error( - "Wrong encryption key", - "The encryption key on this machine does not match the data on the server. \ - You have been logged out.\n\n\ - To fix this, find your existing key by running `atuin key` on a machine that \ - already syncs successfully, then run `atuin login` again here with that key.", - ); - std::process::exit(1); - } - Err(e) => { - // Non-key error (e.g. transient network issue). Don't fail the - // login — the user is authenticated and can sync later when the - // network recovers. - tracing::warn!("could not verify encryption key against remote: {e}"); - Ok(()) - } - } -} - -pub(super) fn or_user_input(value: Option<String>, name: &'static str) -> String { - value.unwrap_or_else(|| read_user_input(name)) -} - -pub(super) fn read_user_password() -> String { - let password = prompt_password("Please enter password: "); - password.expect("Failed to read from input") -} - -fn read_user_input(name: &'static str) -> String { - eprint!("Please enter {name}: "); - get_input().expect("Failed to read from input") -} diff --git a/crates/turtle/src/command/client/account/logout.rs b/crates/turtle/src/command/client/account/logout.rs deleted file mode 100644 index 5708e34c..00000000 --- a/crates/turtle/src/command/client/account/logout.rs +++ /dev/null @@ -1,5 +0,0 @@ -use eyre::Result; - -pub(crate) async fn run() -> Result<()> { - crate::atuin_client::logout::logout().await -} diff --git a/crates/turtle/src/command/client/account/register.rs b/crates/turtle/src/command/client/account/register.rs deleted file mode 100644 index 64fb9f8d..00000000 --- a/crates/turtle/src/command/client/account/register.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clap::Parser; -use eyre::{Result, bail}; - -use super::login::or_user_input; -use crate::atuin_client::settings::{Settings, SyncAuth}; - -#[derive(Parser, Debug)] -pub(crate) struct Cmd { - #[clap(long, short)] - pub(crate) username: Option<String>, - - #[clap(long, short)] - pub(crate) password: Option<String>, - - #[clap(long, short)] - pub(crate) email: Option<String>, -} - -impl Cmd { - pub(crate) async fn run(&self, settings: &Settings) -> Result<()> { - match settings.resolve_sync_auth().await { - SyncAuth::Legacy { .. } => { - println!("You are already logged in."); - println!("Run 'atuin logout' to log out."); - return Ok(()); - } - - SyncAuth::NotLoggedIn { .. } => {} - } - - // Legacy registration flow - println!("Registering for an Atuin Sync account"); - - let username = or_user_input(self.username.clone(), "username"); - let email = or_user_input(self.email.clone(), "email"); - let password = self - .password - .clone() - .unwrap_or_else(super::login::read_user_password); - - if password.is_empty() { - bail!("please provide a password"); - } - - let session = crate::atuin_client::api_client::register( - settings.sync_address.as_str(), - &username, - &email, - &password, - ) - .await?; - - let meta = Settings::meta_store().await?; - meta.save_session(&session.session).await?; - - let _key = crate::atuin_client::encryption::load_key(settings)?; - - println!( - "Registration successful! Please make a note of your key (run 'atuin key') and keep it safe." - ); - println!( - "You will need it to log in on other devices, and we cannot help recover it if you lose it." - ); - - Ok(()) - } -} |
