diff options
Diffstat (limited to 'src/bin')
-rw-r--r-- | src/bin/rbw-agent/actions.rs | 354 | ||||
-rw-r--r-- | src/bin/rbw-agent/agent.rs | 21 | ||||
-rw-r--r-- | src/bin/rbw/actions.rs | 12 | ||||
-rw-r--r-- | src/bin/rbw/commands.rs | 15 | ||||
-rw-r--r-- | src/bin/rbw/main.rs | 17 |
5 files changed, 183 insertions, 236 deletions
diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index d29321c..5918e2c 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -1,10 +1,86 @@ use anyhow::Context as _; +pub async fn register( + sock: &mut crate::sock::Sock, + tty: Option<&str>, +) -> anyhow::Result<()> { + let db = load_db().await.unwrap_or_else(|_| rbw::db::Db::new()); + + if db.needs_login() { + let url_str = config_base_url().await?; + let url = reqwest::Url::parse(&url_str) + .context("failed to parse base url")?; + let host = if let Some(host) = url.host_str() { + host + } else { + return Err(anyhow::anyhow!( + "couldn't find host in rbw base url {}", + url_str + )); + }; + + let email = config_email().await?; + + let mut err_msg = None; + for i in 1_u8..=3 { + let err = if i > 1 { + // this unwrap is safe because we only ever continue the loop + // if we have set err_msg + Some(format!("{} (attempt {}/3)", err_msg.unwrap(), i)) + } else { + None + }; + let client_id = rbw::pinentry::getpin( + &config_pinentry().await?, + "API key client_id", + &format!("Log in to {}", host), + err.as_deref(), + tty, + ) + .await + .context("failed to read client_id from pinentry")?; + let client_secret = rbw::pinentry::getpin( + &config_pinentry().await?, + "API key client_secret", + &format!("Log in to {}", host), + err.as_deref(), + tty, + ) + .await + .context("failed to read client_secret from pinentry")?; + let apikey = rbw::locked::ApiKey::new(client_id, client_secret); + match rbw::actions::register(&email, apikey.clone()).await { + Ok(()) => { + break; + } + Err(rbw::error::Error::IncorrectPassword { message }) => { + if i == 3 { + return Err(rbw::error::Error::IncorrectPassword { + message, + }) + .context("failed to log in to bitwarden instance"); + } else { + err_msg = Some(message); + continue; + } + } + Err(e) => { + return Err(e) + .context("failed to log in to bitwarden instance") + } + } + } + } + + respond_ack(sock).await?; + + Ok(()) +} + pub async fn login( sock: &mut crate::sock::Sock, state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>, tty: Option<&str>, - apikey: bool, ) -> anyhow::Result<()> { let db = load_db().await.unwrap_or_else(|_| rbw::db::Db::new()); @@ -24,66 +100,51 @@ pub async fn login( let email = config_email().await?; let mut err_msg = None; - if apikey { - for i in 1_u8..=3 { - let err = if i > 1 { - // this unwrap is safe because we only ever continue the loop - // if we have set err_msg - Some(format!("{} (attempt {}/3)", err_msg.unwrap(), i)) - } else { - None - }; - let client_id = rbw::pinentry::getpin( - &config_pinentry().await?, - "API key client_id", - &format!("Log in to {}", host), - err.as_deref(), - tty, - ) - .await - .context("failed to read client_id from pinentry")?; - let client_secret = rbw::pinentry::getpin( - &config_pinentry().await?, - "API key client_secret", - &format!("Log in to {}", host), - err.as_deref(), - tty, - ) + for i in 1_u8..=3 { + let err = if i > 1 { + // this unwrap is safe because we only ever continue the loop + // if we have set err_msg + Some(format!("{} (attempt {}/3)", err_msg.unwrap(), i)) + } else { + None + }; + let password = rbw::pinentry::getpin( + &config_pinentry().await?, + "Master Password", + &format!("Log in to {}", host), + err.as_deref(), + tty, + ) + .await + .context("failed to read password from pinentry")?; + match rbw::actions::login(&email, password.clone(), None, None) .await - .context("failed to read client_secret from pinentry")?; - let creds = rbw::locked::LoginCredentials::from_apikey( - rbw::locked::ApiKey::new(client_id, client_secret), - ); - match rbw::actions::login(&email, creds.clone(), None, None) - .await - { - Ok(( + { + Ok(( + access_token, + refresh_token, + iterations, + protected_key, + )) => { + login_success( + sock, + state, access_token, refresh_token, iterations, protected_key, - )) => { - login_success( - sock, - state, - access_token, - refresh_token, - iterations, - protected_key, - None, - db, - email, - ) - .await?; - break; - } - Err(rbw::error::Error::TwoFactorRequired { - providers, - }) => { - if providers.contains( - &rbw::api::TwoFactorProviderType::Authenticator, - ) { - let ( + password, + db, + email, + ) + .await?; + break; + } + Err(rbw::error::Error::TwoFactorRequired { providers }) => { + if providers.contains( + &rbw::api::TwoFactorProviderType::Authenticator, + ) { + let ( access_token, refresh_token, iterations, @@ -91,79 +152,10 @@ pub async fn login( ) = two_factor( tty, &email, - creds, + password.clone(), rbw::api::TwoFactorProviderType::Authenticator, ) .await?; - login_success( - sock, - state, - access_token, - refresh_token, - iterations, - protected_key, - None, - db, - email, - ) - .await?; - break; - } else { - return Err(anyhow::anyhow!("TODO")); - } - } - Err(rbw::error::Error::IncorrectPassword { message }) => { - if i == 3 { - return Err( - rbw::error::Error::IncorrectPassword { - message, - }, - ) - .context( - "failed to log in to bitwarden instance", - ); - } else { - err_msg = Some(message); - continue; - } - } - Err(e) => { - return Err(e).context( - "failed to log in to bitwarden instance", - ) - } - } - } - } else { - for i in 1_u8..=3 { - let err = if i > 1 { - // this unwrap is safe because we only ever continue the loop - // if we have set err_msg - Some(format!("{} (attempt {}/3)", err_msg.unwrap(), i)) - } else { - None - }; - let password = rbw::pinentry::getpin( - &config_pinentry().await?, - "Master Password", - &format!("Log in to {}", host), - err.as_deref(), - tty, - ) - .await - .context("failed to read password from pinentry")?; - let creds = rbw::locked::LoginCredentials::from_password( - password.clone(), - ); - match rbw::actions::login(&email, creds.clone(), None, None) - .await - { - Ok(( - access_token, - refresh_token, - iterations, - protected_key, - )) => { login_success( sock, state, @@ -171,69 +163,31 @@ pub async fn login( refresh_token, iterations, protected_key, - Some(password), + password, db, email, ) .await?; break; + } else { + return Err(anyhow::anyhow!("TODO")); } - Err(rbw::error::Error::TwoFactorRequired { - providers, - }) => { - if providers.contains( - &rbw::api::TwoFactorProviderType::Authenticator, - ) { - let ( - access_token, - refresh_token, - iterations, - protected_key, - ) = two_factor( - tty, - &email, - creds.clone(), - rbw::api::TwoFactorProviderType::Authenticator, - ) - .await?; - login_success( - sock, - state, - access_token, - refresh_token, - iterations, - protected_key, - Some(password), - db, - email, - ) - .await?; - break; - } else { - return Err(anyhow::anyhow!("TODO")); - } - } - Err(rbw::error::Error::IncorrectPassword { message }) => { - if i == 3 { - return Err( - rbw::error::Error::IncorrectPassword { - message, - }, - ) - .context( - "failed to log in to bitwarden instance", - ); - } else { - err_msg = Some(message); - continue; - } - } - Err(e) => { - return Err(e).context( - "failed to log in to bitwarden instance", - ) + } + Err(rbw::error::Error::IncorrectPassword { message }) => { + if i == 3 { + return Err(rbw::error::Error::IncorrectPassword { + message, + }) + .context("failed to log in to bitwarden instance"); + } else { + err_msg = Some(message); + continue; } } + Err(e) => { + return Err(e) + .context("failed to log in to bitwarden instance") + } } } } @@ -246,7 +200,7 @@ pub async fn login( async fn two_factor( tty: Option<&str>, email: &str, - creds: rbw::locked::LoginCredentials, + password: rbw::locked::Password, provider: rbw::api::TwoFactorProviderType, ) -> anyhow::Result<(String, String, u32, String)> { let mut err_msg = None; @@ -271,7 +225,7 @@ async fn two_factor( .context("code was not valid utf8")?; match rbw::actions::login( email, - creds.clone(), + password.clone(), Some(code), Some(provider), ) @@ -326,7 +280,7 @@ async fn login_success( refresh_token: String, iterations: u32, protected_key: String, - password: Option<rbw::locked::Password>, + password: rbw::locked::Password, mut db: rbw::db::Db, email: String, ) -> anyhow::Result<()> { @@ -348,25 +302,23 @@ async fn login_success( )); }; - if let Some(password) = password { - let res = rbw::actions::unlock( - &email, - &password, - iterations, - &protected_key, - &protected_private_key, - &db.protected_org_keys, - ) - .await; + let res = rbw::actions::unlock( + &email, + &password, + iterations, + &protected_key, + &protected_private_key, + &db.protected_org_keys, + ) + .await; - match res { - Ok((keys, org_keys)) => { - let mut state = state.write().await; - state.priv_key = Some(keys); - state.org_keys = Some(org_keys); - } - Err(e) => return Err(e).context("failed to unlock database"), + match res { + Ok((keys, org_keys)) => { + let mut state = state.write().await; + state.priv_key = Some(keys); + state.org_keys = Some(org_keys); } + Err(e) => return Err(e).context("failed to unlock database"), } Ok(()) diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs index 7ca519b..fae8c7b 100644 --- a/src/bin/rbw-agent/agent.rs +++ b/src/bin/rbw-agent/agent.rs @@ -133,24 +133,13 @@ async fn handle_request( } }; let set_timeout = match &req.action { - rbw::protocol::Action::Login => { - crate::actions::login( - sock, - state.clone(), - req.tty.as_deref(), - false, - ) - .await?; + rbw::protocol::Action::Register => { + crate::actions::register(sock, req.tty.as_deref()).await?; true } - rbw::protocol::Action::LoginApiKey => { - crate::actions::login( - sock, - state.clone(), - req.tty.as_deref(), - true, - ) - .await?; + rbw::protocol::Action::Login => { + crate::actions::login(sock, state.clone(), req.tty.as_deref()) + .await?; true } rbw::protocol::Action::Unlock => { diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs index e0e1e55..39fde15 100644 --- a/src/bin/rbw/actions.rs +++ b/src/bin/rbw/actions.rs @@ -1,12 +1,12 @@ use anyhow::Context as _; use std::io::Read as _; -pub fn login(apikey: bool) -> anyhow::Result<()> { - if apikey { - simple_action(rbw::protocol::Action::LoginApiKey) - } else { - simple_action(rbw::protocol::Action::Login) - } +pub fn register() -> anyhow::Result<()> { + simple_action(rbw::protocol::Action::Register) +} + +pub fn login() -> anyhow::Result<()> { + simple_action(rbw::protocol::Action::Login) } pub fn unlock() -> anyhow::Result<()> { diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index dddd501..9efd966 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -465,16 +465,23 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> { Ok(()) } -pub fn login(apikey: bool) -> anyhow::Result<()> { +pub fn register() -> anyhow::Result<()> { ensure_agent()?; - crate::actions::login(apikey)?; + crate::actions::register()?; + + Ok(()) +} + +pub fn login() -> anyhow::Result<()> { + ensure_agent()?; + crate::actions::login()?; Ok(()) } pub fn unlock() -> anyhow::Result<()> { ensure_agent()?; - crate::actions::login(false)?; + crate::actions::login()?; crate::actions::unlock()?; Ok(()) @@ -489,7 +496,7 @@ pub fn unlocked() -> anyhow::Result<()> { pub fn sync() -> anyhow::Result<()> { ensure_agent()?; - crate::actions::login(false)?; + crate::actions::login()?; crate::actions::sync()?; Ok(()) diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index 2612398..0f2d13e 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -17,14 +17,11 @@ enum Opt { config: Config, }, + #[structopt(about = "Register this device with the Bitwarden server")] + Register, + #[structopt(about = "Log in to the Bitwarden server")] - Login { - #[structopt( - long, - help = "Log in to the Bitwarden server using your user API key (see https://bitwarden.com/help/article/personal-api-key/)" - )] - apikey: bool, - }, + Login, #[structopt(about = "Unlock the local Bitwarden database")] Unlock, @@ -220,7 +217,8 @@ impl Opt { Self::Config { config } => { format!("config {}", config.subcommand_name()) } - Self::Login { .. } => "login".to_string(), + Self::Register => "register".to_string(), + Self::Login => "login".to_string(), Self::Unlock => "unlock".to_string(), Self::Unlocked => "unlocked".to_string(), Self::Sync => "sync".to_string(), @@ -290,7 +288,8 @@ fn main(opt: Opt) { Config::Set { key, value } => commands::config_set(key, value), Config::Unset { key } => commands::config_unset(key), }, - Opt::Login { apikey } => commands::login(*apikey), + Opt::Register => commands::register(), + Opt::Login => commands::login(), Opt::Unlock => commands::unlock(), Opt::Unlocked => commands::unlocked(), Opt::Sync => commands::sync(), |