aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-client
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-05-30 12:49:22 +0100
committerGitHub <noreply@github.com>2024-05-30 12:49:22 +0100
commit467f89c104df40904ef4c6b408507e90fe661724 (patch)
treee93697bdfa14ca6b083b0ea02c85d1d0688e0eba /crates/atuin-client
parentchore(deps): bump rusty_paseto and rusty_paserk (#2054) (diff)
downloadatuin-467f89c104df40904ef4c6b408507e90fe661724.zip
feat(ui): add login/register dialog (#2056)
Diffstat (limited to 'crates/atuin-client')
-rw-r--r--crates/atuin-client/Cargo.toml1
-rw-r--r--crates/atuin-client/src/lib.rs2
-rw-r--r--crates/atuin-client/src/login.rs91
-rw-r--r--crates/atuin-client/src/register.rs23
4 files changed, 117 insertions, 0 deletions
diff --git a/crates/atuin-client/Cargo.toml b/crates/atuin-client/Cargo.toml
index fcff56a3..5b1ae78b 100644
--- a/crates/atuin-client/Cargo.toml
+++ b/crates/atuin-client/Cargo.toml
@@ -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"] }
diff --git a/crates/atuin-client/src/lib.rs b/crates/atuin-client/src/lib.rs
index 66258af3..89e2dca5 100644
--- a/crates/atuin-client/src/lib.rs
+++ b/crates/atuin-client/src/lib.rs
@@ -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;
diff --git a/crates/atuin-client/src/login.rs b/crates/atuin-client/src/login.rs
new file mode 100644
index 00000000..05303464
--- /dev/null
+++ b/crates/atuin-client/src/login.rs
@@ -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)
+}
diff --git a/crates/atuin-client/src/register.rs b/crates/atuin-client/src/register.rs
new file mode 100644
index 00000000..dae01efd
--- /dev/null
+++ b/crates/atuin-client/src/register.rs
@@ -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)
+}