-
-
Notifications
You must be signed in to change notification settings - Fork 507
/
login.rs
127 lines (106 loc) · 4.09 KB
/
login.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::io;
use clap::Parser;
use eyre::{bail, ContextCompat, Result};
use tokio::{fs::File, io::AsyncWriteExt};
use atuin_client::{
api_client,
encryption::{encode_key, Key},
settings::Settings,
};
use atuin_common::api::LoginRequest;
use rpassword::prompt_password;
#[derive(Parser)]
pub struct Cmd {
#[clap(long, short)]
pub username: Option<String>,
#[clap(long, short)]
pub password: Option<String>,
/// The encryption key for your account
#[clap(long, short)]
pub key: Option<String>,
}
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 async fn run(&self, settings: &Settings) -> Result<()> {
let session_path = atuin_common::utils::data_dir().join("session");
if session_path.exists() {
println!(
"You are already logged in! Please run 'atuin logout' if you wish to login again"
);
return Ok(());
}
let username = or_user_input(&self.username, "username");
let key = or_user_input(&self.key, "encryption key");
let password = self.password.clone().unwrap_or_else(read_user_password);
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?;
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::<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
}
}
};
file.write_all(key.as_bytes()).await?;
println!("Logged in!");
Ok(())
}
}
pub(super) fn or_user_input(value: &'_ Option<String>, name: &'static str) -> String {
value.clone().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")
}
#[cfg(test)]
mod tests {
use atuin_client::encryption::Key;
#[test]
fn mnemonic_round_trip() {
let key = Key {
0: [
3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3,
2, 7, 9, 5,
],
};
let phrase = bip39::Mnemonic::from_entropy(&key.0, bip39::Language::English)
.unwrap()
.into_phrase();
let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap();
assert_eq!(mnemonic.entropy(), &key.0);
assert_eq!(phrase, "adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink");
}
}