aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/rbw-agent/actions.rs284
-rw-r--r--src/bin/rbw-agent/agent.rs19
-rw-r--r--src/bin/rbw/actions.rs8
-rw-r--r--src/bin/rbw/commands.rs8
-rw-r--r--src/bin/rbw/main.rs12
5 files changed, 244 insertions, 87 deletions
diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs
index cb30551..d29321c 100644
--- a/src/bin/rbw-agent/actions.rs
+++ b/src/bin/rbw-agent/actions.rs
@@ -4,6 +4,7 @@ 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());
@@ -23,49 +24,66 @@ pub async fn login(
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 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, None, None).await {
- Ok((
- access_token,
- refresh_token,
- iterations,
- protected_key,
- )) => {
- login_success(
- sock,
- state,
+ 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,
+ )
+ .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((
access_token,
refresh_token,
iterations,
protected_key,
- password,
- db,
- email,
- )
- .await?;
- break;
- }
- Err(rbw::error::Error::TwoFactorRequired { providers }) => {
- if providers.contains(
- &rbw::api::TwoFactorProviderType::Authenticator,
- ) {
- let (
+ )) => {
+ 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 (
access_token,
refresh_token,
iterations,
@@ -73,10 +91,79 @@ pub async fn login(
) = two_factor(
tty,
&email,
- &password,
+ creds,
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,
@@ -84,30 +171,68 @@ pub async fn login(
refresh_token,
iterations,
protected_key,
- password,
+ 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(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(e) => {
- return Err(e)
- .context("failed to log in to bitwarden instance")
}
}
}
@@ -121,7 +246,7 @@ pub async fn login(
async fn two_factor(
tty: Option<&str>,
email: &str,
- password: &rbw::locked::Password,
+ creds: rbw::locked::LoginCredentials,
provider: rbw::api::TwoFactorProviderType,
) -> anyhow::Result<(String, String, u32, String)> {
let mut err_msg = None;
@@ -144,8 +269,13 @@ async fn two_factor(
.context("failed to read code from pinentry")?;
let code = std::str::from_utf8(code.password())
.context("code was not valid utf8")?;
- match rbw::actions::login(email, password, Some(code), Some(provider))
- .await
+ match rbw::actions::login(
+ email,
+ creds.clone(),
+ Some(code),
+ Some(provider),
+ )
+ .await
{
Ok((access_token, refresh_token, iterations, protected_key)) => {
return Ok((
@@ -196,7 +326,7 @@ async fn login_success(
refresh_token: String,
iterations: u32,
protected_key: String,
- password: rbw::locked::Password,
+ password: Option<rbw::locked::Password>,
mut db: rbw::db::Db,
email: String,
) -> anyhow::Result<()> {
@@ -218,23 +348,25 @@ async fn login_success(
));
};
- let res = rbw::actions::unlock(
- &email,
- &password,
- iterations,
- &protected_key,
- &protected_private_key,
- &db.protected_org_keys,
- )
- .await;
+ if let Some(password) = password {
+ 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);
+ 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"),
}
- 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 8db8a59..7ca519b 100644
--- a/src/bin/rbw-agent/agent.rs
+++ b/src/bin/rbw-agent/agent.rs
@@ -134,8 +134,23 @@ async fn handle_request(
};
let set_timeout = match &req.action {
rbw::protocol::Action::Login => {
- crate::actions::login(sock, state.clone(), req.tty.as_deref())
- .await?;
+ crate::actions::login(
+ sock,
+ state.clone(),
+ req.tty.as_deref(),
+ false,
+ )
+ .await?;
+ true
+ }
+ rbw::protocol::Action::LoginApiKey => {
+ crate::actions::login(
+ sock,
+ state.clone(),
+ req.tty.as_deref(),
+ true,
+ )
+ .await?;
true
}
rbw::protocol::Action::Unlock => {
diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs
index 75703f9..e0e1e55 100644
--- a/src/bin/rbw/actions.rs
+++ b/src/bin/rbw/actions.rs
@@ -1,8 +1,12 @@
use anyhow::Context as _;
use std::io::Read as _;
-pub fn login() -> anyhow::Result<()> {
- simple_action(rbw::protocol::Action::Login)
+pub fn login(apikey: bool) -> anyhow::Result<()> {
+ if apikey {
+ simple_action(rbw::protocol::Action::LoginApiKey)
+ } else {
+ 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 db385a8..dddd501 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -465,16 +465,16 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> {
Ok(())
}
-pub fn login() -> anyhow::Result<()> {
+pub fn login(apikey: bool) -> anyhow::Result<()> {
ensure_agent()?;
- crate::actions::login()?;
+ crate::actions::login(apikey)?;
Ok(())
}
pub fn unlock() -> anyhow::Result<()> {
ensure_agent()?;
- crate::actions::login()?;
+ crate::actions::login(false)?;
crate::actions::unlock()?;
Ok(())
@@ -489,7 +489,7 @@ pub fn unlocked() -> anyhow::Result<()> {
pub fn sync() -> anyhow::Result<()> {
ensure_agent()?;
- crate::actions::login()?;
+ crate::actions::login(false)?;
crate::actions::sync()?;
Ok(())
diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs
index e6e1249..2612398 100644
--- a/src/bin/rbw/main.rs
+++ b/src/bin/rbw/main.rs
@@ -18,7 +18,13 @@ enum Opt {
},
#[structopt(about = "Log in to the Bitwarden server")]
- Login,
+ 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,
+ },
#[structopt(about = "Unlock the local Bitwarden database")]
Unlock,
@@ -214,7 +220,7 @@ impl Opt {
Self::Config { config } => {
format!("config {}", config.subcommand_name())
}
- Self::Login => "login".to_string(),
+ Self::Login { .. } => "login".to_string(),
Self::Unlock => "unlock".to_string(),
Self::Unlocked => "unlocked".to_string(),
Self::Sync => "sync".to_string(),
@@ -284,7 +290,7 @@ fn main(opt: Opt) {
Config::Set { key, value } => commands::config_set(key, value),
Config::Unset { key } => commands::config_unset(key),
},
- Opt::Login => commands::login(),
+ Opt::Login { apikey } => commands::login(*apikey),
Opt::Unlock => commands::unlock(),
Opt::Unlocked => commands::unlocked(),
Opt::Sync => commands::sync(),