From 2cec7ba677f7b25a6e489a3545219b9965997fda Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Fri, 10 Feb 2023 20:35:38 +0100 Subject: [PATCH] Allow using existing key file on login (#688) * Allow logging in without overwriting existing key file If the given key on login in empty, keep the existing key file rather than overwriting it with an empty file. This is useful if you log out and want to log in again and still use the same key, or if you have copied over the key file rather than providing it as input. * Refuse logging in if key is empty Before the previous commit, an empty key file would be created if key wasn't specified, and after the previous commit, the key file would not be created if the key wasn't specified and stay empty if it was empty. Now the log command checks the key file if a key is not specified and exits with an error message if either the key file couldn't be opened or is empty. If a key is specified, the key file is just created with it as before. * Validate the key on login, create new if no exists After reading the key either from an existing key file, or from the user input, validate that the provided key is valid (rather than just checking that it isn't empty). If no key file exists, create a new key instead of erroring out. --- src/command/client/sync/login.rs | 86 ++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/src/command/client/sync/login.rs b/src/command/client/sync/login.rs index bd3a8029..06e65196 100644 --- a/src/command/client/sync/login.rs +++ b/src/command/client/sync/login.rs @@ -1,12 +1,12 @@ -use std::io; +use std::{io, path::PathBuf}; use clap::Parser; -use eyre::{bail, ContextCompat, Result}; +use eyre::{bail, Context, ContextCompat, Result}; use tokio::{fs::File, io::AsyncWriteExt}; use atuin_client::{ api_client, - encryption::{encode_key, Key}, + encryption::{decode_key, encode_key, new_key, Key}, settings::Settings, }; use atuin_common::api::LoginRequest; @@ -44,8 +44,57 @@ impl Cmd { } let username = or_user_input(&self.username, "username"); - let key = or_user_input(&self.key, "encryption key"); + let key = or_user_input(&self.key, "encryption key [blank to use existing key file]"); let password = self.password.clone().unwrap_or_else(read_user_password); + + let key_path = settings.key_path.as_str(); + if key.is_empty() { + if PathBuf::from(key_path).exists() { + let bytes = fs_err::read_to_string(key_path) + .context("existing key file couldn't be read")?; + if decode_key(bytes).is_err() { + bail!("the key in existing key file was invalid"); + } + } else { + println!("No key file exists, creating a new"); + let _key = new_key(settings)?; + } + } else { + // try parse the key as a mnemonic... + let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { + Ok(mnemonic) => encode_key( + Key::from_slice(mnemonic.entropy()) + .context("key was not the correct length")?, + )?, + Err(err) => { + if let Some(err) = err.downcast_ref::() { + match err { + // assume they copied in the base64 key + bip39::ErrorKind::InvalidWord => key, + bip39::ErrorKind::InvalidChecksum => { + bail!("key mnemonic was not valid") + } + bip39::ErrorKind::InvalidKeysize(_) + | bip39::ErrorKind::InvalidWordLength(_) + | bip39::ErrorKind::InvalidEntropyLength(_, _) => { + bail!("key was not the correct length") + } + } + } else { + // unknown error. assume they copied the base64 key + key + } + } + }; + + if decode_key(key.clone()).is_err() { + bail!("the specified key was invalid"); + } + + let mut file = File::create(key_path).await?; + file.write_all(key.as_bytes()).await?; + } + let session = api_client::login( settings.sync_address.as_str(), LoginRequest { username, password }, @@ -56,35 +105,6 @@ impl Cmd { let mut file = File::create(session_path).await?; file.write_all(session.session.as_bytes()).await?; - let key_path = settings.key_path.as_str(); - let mut file = File::create(key_path).await?; - - // try parse the key as a mnemonic... - let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { - Ok(mnemonic) => encode_key( - Key::from_slice(mnemonic.entropy()).context("key was not the correct length")?, - )?, - Err(err) => { - if let Some(err) = err.downcast_ref::() { - match err { - // assume they copied in the base64 key - bip39::ErrorKind::InvalidWord => key, - bip39::ErrorKind::InvalidChecksum => bail!("key mnemonic was not valid"), - bip39::ErrorKind::InvalidKeysize(_) - | bip39::ErrorKind::InvalidWordLength(_) - | bip39::ErrorKind::InvalidEntropyLength(_, _) => { - bail!("key was not the correct length") - } - } - } else { - // unknown error. assume they copied the base64 key - key - } - } - }; - - file.write_all(key.as_bytes()).await?; - println!("Logged in!"); Ok(())