Skip to content

Commit

Permalink
feat(ui): add login/register dialog (#2056)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellie authored May 30, 2024
1 parent 15618f1 commit 467f89c
Show file tree
Hide file tree
Showing 18 changed files with 779 additions and 104 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/atuin-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ reqwest = { workspace = true, optional = true }
hex = { version = "0.4", optional = true }
sha2 = { version = "0.10", optional = true }
indicatif = "0.17.7"
tiny-bip39 = "1"

[dev-dependencies]
tokio = { version = "1", features = ["full"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/atuin-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ pub mod encryption;
pub mod history;
pub mod import;
pub mod kv;
pub mod login;
pub mod ordering;
pub mod record;
pub mod register;
pub mod secrets;
pub mod settings;

Expand Down
91 changes: 91 additions & 0 deletions crates/atuin-client/src/login.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::path::PathBuf;

use atuin_common::api::LoginRequest;
use eyre::{bail, Context, Result};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;

use crate::{
api_client,
encryption::{decode_key, encode_key, load_key, Key},
record::{sqlite_store::SqliteStore, store::Store},
settings::Settings,
};

pub async fn login(
settings: &Settings,
store: &SqliteStore,
username: String,
password: String,
key: String,
) -> Result<String> {
// 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()))?,
Err(err) => {
if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() {
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
}
}
};

let key_path = settings.key_path.as_str();
let key_path = PathBuf::from(key_path);

if !key_path.exists() {
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?;
} 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")?
.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?;
}
}

let session = api_client::login(
settings.sync_address.as_str(),
LoginRequest { username, password },
)
.await?;

let session_path = settings.session_path.as_str();
let mut file = File::create(session_path).await?;
file.write_all(session.session.as_bytes()).await?;

Ok(session.session)
}
23 changes: 23 additions & 0 deletions crates/atuin-client/src/register.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use eyre::Result;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;

use crate::{api_client, settings::Settings};

pub async fn register(
settings: &Settings,
username: String,
email: String,
password: String,
) -> Result<String> {
let session =
api_client::register(settings.sync_address.as_str(), &username, &email, &password).await?;

let path = settings.session_path.as_str();
let mut file = File::create(path).await?;
file.write_all(session.session.as_bytes()).await?;

let _key = crate::encryption::load_key(settings)?;

Ok(session.session)
}
6 changes: 6 additions & 0 deletions crates/atuin/src/command/client/account/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ fn get_input() -> Result<String> {

impl Cmd {
pub async fn run(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
// TODO(ellie): Replace this with a call to atuin_client::login::login
// The reason I haven't done this yet is that this implementation allows for
// an empty key. This will use an existing key file.
//
// I'd quite like to ditch that behaviour, so have not brought it into the library
// function.
if settings.logged_in() {
println!(
"You are already logged in! Please run 'atuin logout' if you wish to login again"
Expand Down
Loading

0 comments on commit 467f89c

Please sign in to comment.