aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/account/login.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/turtle/src/command/client/account/login.rs')
-rw-r--r--crates/turtle/src/command/client/account/login.rs201
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(&current_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")
-}