diff options
Diffstat (limited to 'crates/turtle/src/command/client/account/login.rs')
| -rw-r--r-- | crates/turtle/src/command/client/account/login.rs | 201 |
1 files changed, 0 insertions, 201 deletions
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") -} |
