From acf0298664902ab56b5ebe00484750d55994e700 Mon Sep 17 00:00:00 2001 From: Hinton Date: Thu, 15 Aug 2024 09:52:36 +0200 Subject: [PATCH 1/5] Showcase how state can be used in the cli --- Cargo.lock | 6 ++ .../bitwarden-core/src/auth/login/api_key.rs | 34 ++++++- crates/bitwarden-core/src/auth/login/mod.rs | 12 +++ .../src/platform/client_platform.rs | 11 ++- crates/bitwarden-core/src/platform/mod.rs | 2 + .../src/platform/settings_repository.rs | 43 +++++++++ crates/bw/Cargo.toml | 13 ++- crates/bw/src/auth/login.rs | 7 ++ crates/bw/src/commands/mod.rs | 1 + crates/bw/src/commands/vault.rs | 77 +++++++++++++++ crates/bw/src/main.rs | 19 ++-- crates/bw/src/render.rs | 95 +++++++++++++++++++ 12 files changed, 304 insertions(+), 16 deletions(-) create mode 100644 crates/bitwarden-core/src/platform/settings_repository.rs create mode 100644 crates/bw/src/commands/mod.rs create mode 100644 crates/bw/src/commands/vault.rs diff --git a/Cargo.lock b/Cargo.lock index 9cacc82ba..a0ee721e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,14 +771,20 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" name = "bw" version = "0.0.2" dependencies = [ + "bat", "bitwarden", "bitwarden-cli", "bitwarden-crypto", + "chrono", "clap", "color-eyre", + "comfy-table", "env_logger", "inquire", "log", + "serde", + "serde_json", + "serde_yaml", "tempfile", "tokio", ] diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index cce246528..bc023ad61 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -13,6 +13,9 @@ use crate::{ require, Client, }; +#[cfg(feature = "state")] +use super::AuthSettings; + pub(crate) async fn login_api_key( client: &Client, input: &ApiKeyLoginRequest, @@ -45,16 +48,37 @@ pub(crate) async fn login_api_key( .set_login_method(LoginMethod::User(UserLoginMethod::ApiKey { client_id: input.client_id.to_owned(), client_secret: input.client_secret.to_owned(), - email, - kdf, + email: email.clone(), + kdf: kdf.clone(), })); let user_key: EncString = require!(r.key.as_deref()).parse()?; let private_key: EncString = require!(r.private_key.as_deref()).parse()?; - client - .internal - .initialize_user_crypto_master_key(master_key, user_key, private_key)?; + client.internal.initialize_user_crypto_master_key( + master_key, + user_key.clone(), + private_key.clone(), + )?; + + #[cfg(feature = "state")] + { + let setting = AuthSettings { + email: email.clone(), + token: r.access_token.clone(), + refresh_token: r.refresh_token.clone(), + kdf: kdf.clone(), + user_key: user_key.to_string(), + private_key: private_key.to_string(), + }; + + client + .platform() + .settings_repository + .set("auth", &serde_json::to_string(&setting)?) + .await + .unwrap(); + } } ApiKeyLoginResponse::process_response(response) diff --git a/crates/bitwarden-core/src/auth/login/mod.rs b/crates/bitwarden-core/src/auth/login/mod.rs index 0c90dc973..3652db9ed 100644 --- a/crates/bitwarden-core/src/auth/login/mod.rs +++ b/crates/bitwarden-core/src/auth/login/mod.rs @@ -92,3 +92,15 @@ pub(crate) fn parse_prelogin(response: PreloginResponseModel) -> Result { }, }) } + +#[cfg(feature = "state")] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct AuthSettings { + pub email: String, + pub token: String, + pub refresh_token: Option, + pub kdf: Kdf, + + pub user_key: String, + pub private_key: String, +} diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs index 1f117d5fe..99df0cdd9 100644 --- a/crates/bitwarden-core/src/platform/client_platform.rs +++ b/crates/bitwarden-core/src/platform/client_platform.rs @@ -5,8 +5,13 @@ use super::{ }; use crate::{error::Result, Client}; +#[cfg(feature = "state")] +use super::settings_repository::SettingsRepository; + pub struct ClientPlatform<'a> { pub(crate) client: &'a Client, + #[cfg(feature = "state")] + pub settings_repository: SettingsRepository, } impl<'a> ClientPlatform<'a> { @@ -28,6 +33,10 @@ impl<'a> ClientPlatform<'a> { impl<'a> Client { pub fn platform(&'a self) -> ClientPlatform<'a> { - ClientPlatform { client: self } + ClientPlatform { + client: self, + #[cfg(feature = "state")] + settings_repository: SettingsRepository::new(self.internal.db.clone()), + } } } diff --git a/crates/bitwarden-core/src/platform/mod.rs b/crates/bitwarden-core/src/platform/mod.rs index 031554be0..50ae497e8 100644 --- a/crates/bitwarden-core/src/platform/mod.rs +++ b/crates/bitwarden-core/src/platform/mod.rs @@ -2,6 +2,8 @@ pub mod client_platform; mod generate_fingerprint; mod get_user_api_key; mod secret_verification_request; +#[cfg(feature = "state")] +mod settings_repository; pub use generate_fingerprint::{FingerprintRequest, FingerprintResponse}; pub(crate) use get_user_api_key::get_user_api_key; diff --git a/crates/bitwarden-core/src/platform/settings_repository.rs b/crates/bitwarden-core/src/platform/settings_repository.rs new file mode 100644 index 000000000..f04e48a3a --- /dev/null +++ b/crates/bitwarden-core/src/platform/settings_repository.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use bitwarden_db::{params, Database, DatabaseError, DatabaseTrait}; +use tokio::sync::Mutex; + +pub struct SettingsRepository { + db: Arc>, +} + +impl SettingsRepository { + pub fn new(db: Arc>) -> Self { + Self { db: db.clone() } + } + + pub async fn get(&self, key: &str) -> Result, DatabaseError> { + let guard = self.db.lock().await; + + let res = guard + .query_map( + "SELECT value FROM settings WHERE key = ?1", + [key], + |row| -> Result { row.get(0) }, + ) + .await? + .first() + .map(|x| x.to_owned()); + + Ok(res) + } + + pub async fn set(&self, key: &str, value: &str) -> Result<(), DatabaseError> { + let guard = self.db.lock().await; + + guard + .execute( + "INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)", + params![key, value], + ) + .await?; + + Ok(()) + } +} diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 7361f15b6..800958789 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -14,15 +14,26 @@ repository.workspace = true license-file.workspace = true [dependencies] -bitwarden = { workspace = true, features = ["internal"] } +bat = { version = "0.24.0", features = [ + "regex-onig", +], default-features = false } +bitwarden = { workspace = true, features = ["internal", "state"] } bitwarden-cli = { workspace = true } bitwarden-crypto = { workspace = true } +chrono = { version = "0.4.38", features = [ + "clock", + "std", +], default-features = false } clap = { version = "4.5.4", features = ["derive", "env"] } +comfy-table = "7.1.1" color-eyre = "0.6.3" env_logger = "0.11.1" inquire = "0.7.0" log = "0.4.20" tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +serde = "1.0.196" +serde_json = ">=1.0.96, <2" +serde_yaml = "0.9" [dev-dependencies] tempfile = "3.10.0" diff --git a/crates/bw/src/auth/login.rs b/crates/bw/src/auth/login.rs index 51fe64a39..abfa2bc94 100644 --- a/crates/bw/src/auth/login.rs +++ b/crates/bw/src/auth/login.rs @@ -111,6 +111,13 @@ pub(crate) async fn login_api_key( }) .await?; + let res = client + .vault() + .sync(&SyncRequest { + exclude_subdomains: Some(true), + }) + .await?; + debug!("{:?}", result); Ok(()) diff --git a/crates/bw/src/commands/mod.rs b/crates/bw/src/commands/mod.rs new file mode 100644 index 000000000..f0731793c --- /dev/null +++ b/crates/bw/src/commands/mod.rs @@ -0,0 +1 @@ +pub(crate) mod vault; diff --git a/crates/bw/src/commands/vault.rs b/crates/bw/src/commands/vault.rs new file mode 100644 index 000000000..758be994a --- /dev/null +++ b/crates/bw/src/commands/vault.rs @@ -0,0 +1,77 @@ +use bitwarden::{ + auth::login::AuthSettings, + mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, + vault::{CipherListView, ClientVaultExt}, + Client, Error, +}; +use bitwarden_cli::Color; + +use clap::Subcommand; + +use crate::render::{serialize_response, Output, OutputSettings, TableSerialize}; + +#[derive(Subcommand, Clone)] +pub(crate) enum VaultCommands { + Get { id: String }, + List {}, + Create {}, +} + +pub(crate) async fn process_command( + command: VaultCommands, + client: Client, + password: Option, +) -> Result<(), Error> { + // TODO: This should be moved into the SDK + let setting = client + .platform() + .settings_repository + .get("auth") + .await + .unwrap() + .unwrap(); + let setting = serde_json::from_str::(&setting)?; + + client + .crypto() + .initialize_user_crypto(InitUserCryptoRequest { + kdf_params: setting.kdf, + email: setting.email, + private_key: setting.private_key, + method: InitUserCryptoMethod::Password { + password: password.unwrap(), + user_key: setting.user_key, + }, + }) + .await + .unwrap(); + + match command { + VaultCommands::Get { id } => todo!(), + VaultCommands::List {} => { + let ciphers = client.vault().cipher_repository.get_all().await.unwrap(); + + let dec = client.vault().ciphers().decrypt_list(ciphers)?; + + /*for cipher in dec { + println!("{}", cipher.name); + }*/ + + let output_settings = OutputSettings::new(Output::Table, Color::Auto); + serialize_response(dec, output_settings); + + Ok(()) + } + VaultCommands::Create {} => todo!(), + } +} + +impl TableSerialize<2> for CipherListView { + fn get_headers() -> [&'static str; 2] { + ["ID", "Name"] + } + + fn get_values(&self) -> Vec<[String; 2]> { + vec![[self.id.unwrap_or_default().to_string(), self.name.clone()]] + } +} diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index f9938c1a9..0bc5b1c84 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -10,8 +10,11 @@ use inquire::Password; use render::Output; mod auth; +mod commands; mod render; +use commands::vault::{self, VaultCommands}; + #[derive(Parser, Clone)] #[command(name = "Bitwarden CLI", version, about = "Bitwarden CLI", long_about = None)] struct Cli { @@ -44,9 +47,11 @@ enum Commands { }, #[command(long_about = "Manage vault items")] - Item { + Vault { #[command(subcommand)] - command: ItemCommands, + command: VaultCommands, + #[arg(long, global = true, help = "Master password")] + password: Option, }, #[command(long_about = "Pull the latest vault data from the server")] @@ -85,12 +90,6 @@ enum LoginCommands { }, } -#[derive(Subcommand, Clone)] -enum ItemCommands { - Get { id: String }, - Create {}, -} - #[derive(Subcommand, Clone)] enum GeneratorCommands { Password(PasswordGeneratorArgs), @@ -213,7 +212,9 @@ async fn process_commands() -> Result<()> { match command { Commands::Login(_) => unreachable!(), Commands::Register { .. } => unreachable!(), - Commands::Item { command: _ } => todo!(), + Commands::Vault { command, password } => { + vault::process_command(command, client, password).await? + } Commands::Sync {} => todo!(), Commands::Generate { command } => match command { GeneratorCommands::Password(args) => { diff --git a/crates/bw/src/render.rs b/crates/bw/src/render.rs index da8ed4997..433960380 100644 --- a/crates/bw/src/render.rs +++ b/crates/bw/src/render.rs @@ -1,4 +1,8 @@ +use bitwarden_cli::Color; +use chrono::{DateTime, Utc}; use clap::ValueEnum; +use comfy_table::Table; +use serde::Serialize; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] #[allow(clippy::upper_case_acronyms)] @@ -9,3 +13,94 @@ pub(crate) enum Output { TSV, None, } + +const ASCII_HEADER_ONLY: &str = " -- "; + +pub(crate) struct OutputSettings { + pub(crate) output: Output, + pub(crate) color: Color, +} + +impl OutputSettings { + pub(crate) fn new(output: Output, color: Color) -> Self { + OutputSettings { output, color } + } +} + +pub(crate) fn serialize_response, const N: usize>( + data: T, + output_settings: OutputSettings, +) { + match output_settings.output { + Output::JSON => { + let mut text = + serde_json::to_string_pretty(&data).expect("Serialize should be infallible"); + // Yaml/table/tsv serializations add a newline at the end, so we do the same here for + // consistency + text.push('\n'); + pretty_print("json", &text, output_settings.color); + } + Output::YAML => { + let text = serde_yaml::to_string(&data).expect("Serialize should be infallible"); + pretty_print("yaml", &text, output_settings.color); + } + Output::Table => { + let mut table = Table::new(); + table + .load_preset(ASCII_HEADER_ONLY) + .set_header(T::get_headers()) + .add_rows(data.get_values()); + + println!("{table}"); + } + Output::TSV => { + println!("{}", T::get_headers().join("\t")); + + let rows: Vec = data + .get_values() + .into_iter() + .map(|row| row.join("\t")) + .collect(); + println!("{}", rows.join("\n")); + } + Output::None => {} + } +} + +fn pretty_print(language: &str, data: &str, color: Color) { + if color.is_enabled() { + bat::PrettyPrinter::new() + .input_from_bytes(data.as_bytes()) + .language(language) + .print() + .expect("Input is valid"); + } else { + print!("{}", data); + } +} + +// We're using const generics for the array lengths to make sure the header count and value count +// match +pub(crate) trait TableSerialize: Sized { + fn get_headers() -> [&'static str; N]; + fn get_values(&self) -> Vec<[String; N]>; +} + +// Generic impl for Vec so we can call `serialize_response` with both individual +// elements and lists of elements, like we do with the JSON and YAML cases +impl, const N: usize> TableSerialize for Vec { + fn get_headers() -> [&'static str; N] { + T::get_headers() + } + fn get_values(&self) -> Vec<[String; N]> { + let mut values = Vec::new(); + for t in self { + values.append(&mut t.get_values()); + } + values + } +} + +fn format_date(date: &DateTime) -> String { + date.format("%Y-%m-%d %H:%M:%S").to_string() +} From 45edbfca62a4e65e8b1dba298bccbe3794397893 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 16 Aug 2024 14:47:59 +0200 Subject: [PATCH 2/5] Cleanup auth state --- .../src/auth/auth_repository.rs | 59 +++++++++++++++++++ crates/bitwarden-core/src/auth/client_auth.rs | 22 +++++-- .../bitwarden-core/src/auth/login/api_key.rs | 10 ++-- crates/bitwarden-core/src/auth/login/mod.rs | 12 ---- crates/bitwarden-core/src/auth/mod.rs | 14 +++++ crates/bitwarden-core/src/auth/unlock.rs | 40 +++++++++++++ crates/bitwarden-core/src/platform/mod.rs | 2 + crates/bw/src/commands/vault.rs | 29 +-------- 8 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 crates/bitwarden-core/src/auth/auth_repository.rs create mode 100644 crates/bitwarden-core/src/auth/unlock.rs diff --git a/crates/bitwarden-core/src/auth/auth_repository.rs b/crates/bitwarden-core/src/auth/auth_repository.rs new file mode 100644 index 000000000..38506e76d --- /dev/null +++ b/crates/bitwarden-core/src/auth/auth_repository.rs @@ -0,0 +1,59 @@ +use bitwarden_crypto::Kdf; +use bitwarden_db::DatabaseError; +use thiserror::Error; + +use crate::platform::SettingsRepository; + +const SETTINGS_KEY: &str = "auth"; + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct AuthSettings { + pub email: String, + pub token: String, + pub refresh_token: Option, + pub kdf: Kdf, + + pub user_key: String, + pub private_key: String, +} + +#[derive(Debug, Error)] +pub enum AuthRepositoryError { + #[error(transparent)] + Database(#[from] DatabaseError), + #[error(transparent)] + Serde(#[from] serde_json::Error), +} + +pub struct AuthRepository { + settings_repository: SettingsRepository, +} + +impl AuthRepository { + pub fn new(settings_repository: SettingsRepository) -> Self { + Self { + settings_repository, + } + } + + pub(crate) async fn save(&self, setting: AuthSettings) -> Result<(), AuthRepositoryError> { + let serialized = serde_json::to_string(&setting)?; + self.settings_repository + .set(SETTINGS_KEY, &serialized) + .await?; + + Ok(()) + } + + pub(crate) async fn get(&self) -> Result, AuthRepositoryError> { + let settings = self.settings_repository.get(SETTINGS_KEY).await?; + + match settings { + Some(settings) => { + let settings: AuthSettings = serde_json::from_str(&settings)?; + Ok(Some(settings)) + } + None => Ok(None), + } + } +} diff --git a/crates/bitwarden-core/src/auth/client_auth.rs b/crates/bitwarden-core/src/auth/client_auth.rs index 6f1afb135..0f48021ed 100644 --- a/crates/bitwarden-core/src/auth/client_auth.rs +++ b/crates/bitwarden-core/src/auth/client_auth.rs @@ -23,8 +23,14 @@ use crate::auth::{ }; use crate::{auth::renew::renew_token, error::Result, Client}; +use super::UnlockError; +#[cfg(feature = "state")] +use super::{unlock, AuthRepository}; + pub struct ClientAuth<'a> { pub(crate) client: &'a crate::Client, + #[cfg(feature = "state")] + pub(super) repository: AuthRepository, } impl<'a> ClientAuth<'a> { @@ -132,10 +138,7 @@ impl<'a> ClientAuth<'a> { pub fn trust_device(&self) -> Result { trust_device(self.client) } -} -#[cfg(feature = "internal")] -impl<'a> ClientAuth<'a> { pub async fn login_device( &self, email: String, @@ -153,6 +156,13 @@ impl<'a> ClientAuth<'a> { } } +#[cfg(feature = "state")] +impl<'a> ClientAuth<'a> { + pub async fn unlock(&self, password: String) -> Result<(), UnlockError> { + unlock(self.client, password).await + } +} + #[cfg(feature = "internal")] fn trust_device(client: &Client) -> Result { let enc = client.internal.get_encryption_settings()?; @@ -164,7 +174,11 @@ fn trust_device(client: &Client) -> Result { impl<'a> Client { pub fn auth(&'a self) -> ClientAuth<'a> { - ClientAuth { client: self } + ClientAuth { + client: self, + #[cfg(feature = "state")] + repository: AuthRepository::new(self.platform().settings_repository), + } } } diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index bc023ad61..25bfea5b7 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -14,7 +14,7 @@ use crate::{ }; #[cfg(feature = "state")] -use super::AuthSettings; +use crate::auth::AuthSettings; pub(crate) async fn login_api_key( client: &Client, @@ -73,11 +73,11 @@ pub(crate) async fn login_api_key( }; client - .platform() - .settings_repository - .set("auth", &serde_json::to_string(&setting)?) + .auth() + .repository + .save(setting) .await - .unwrap(); + .expect("Save settings"); } } diff --git a/crates/bitwarden-core/src/auth/login/mod.rs b/crates/bitwarden-core/src/auth/login/mod.rs index 3652db9ed..0c90dc973 100644 --- a/crates/bitwarden-core/src/auth/login/mod.rs +++ b/crates/bitwarden-core/src/auth/login/mod.rs @@ -92,15 +92,3 @@ pub(crate) fn parse_prelogin(response: PreloginResponseModel) -> Result { }, }) } - -#[cfg(feature = "state")] -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct AuthSettings { - pub email: String, - pub token: String, - pub refresh_token: Option, - pub kdf: Kdf, - - pub user_key: String, - pub private_key: String, -} diff --git a/crates/bitwarden-core/src/auth/mod.rs b/crates/bitwarden-core/src/auth/mod.rs index e52870570..558c748ce 100644 --- a/crates/bitwarden-core/src/auth/mod.rs +++ b/crates/bitwarden-core/src/auth/mod.rs @@ -34,6 +34,20 @@ pub use tde::RegisterTdeKeyResponse; #[cfg(feature = "internal")] use crate::error::Result; +#[cfg(feature = "state")] +#[path = ""] +mod state { + mod auth_repository; + pub use auth_repository::{AuthRepository, AuthSettings}; + + mod unlock; + pub(super) use unlock::unlock; + pub use unlock::UnlockError; +} + +#[cfg(feature = "state")] +pub use state::*; + #[cfg(feature = "internal")] fn determine_password_hash( email: &str, diff --git a/crates/bitwarden-core/src/auth/unlock.rs b/crates/bitwarden-core/src/auth/unlock.rs new file mode 100644 index 000000000..98fb96527 --- /dev/null +++ b/crates/bitwarden-core/src/auth/unlock.rs @@ -0,0 +1,40 @@ +use bitwarden_crypto::{CryptoError, MasterKey}; +use thiserror::Error; + +use crate::Client; + +use super::auth_repository::AuthRepositoryError; + +#[derive(Debug, Error)] +pub enum UnlockError { + #[error(transparent)] + AuthRepository(#[from] AuthRepositoryError), + + #[error(transparent)] + Crypto(#[from] CryptoError), + + #[error("The client is not authenticated or the session has expired")] + NotAuthenticated, +} + +pub(crate) async fn unlock( + client: &Client, + password: String, +) -> std::result::Result<(), UnlockError> { + let settings = client + .auth() + .repository + .get() + .await? + .ok_or(UnlockError::NotAuthenticated)?; + + // client.internal.set_login_method(UserLoginMethod::ApiKey { client_id: (), client_secret: (), email: (), kdf: () }) + let master_key = MasterKey::derive(&password, &settings.email, &settings.kdf)?; + client.internal.initialize_user_crypto_master_key( + master_key, + settings.user_key.parse()?, + settings.private_key.parse()?, + ); + + Ok(()) +} diff --git a/crates/bitwarden-core/src/platform/mod.rs b/crates/bitwarden-core/src/platform/mod.rs index 50ae497e8..d7a134bc8 100644 --- a/crates/bitwarden-core/src/platform/mod.rs +++ b/crates/bitwarden-core/src/platform/mod.rs @@ -4,6 +4,8 @@ mod get_user_api_key; mod secret_verification_request; #[cfg(feature = "state")] mod settings_repository; +#[cfg(feature = "state")] +pub use settings_repository::SettingsRepository; pub use generate_fingerprint::{FingerprintRequest, FingerprintResponse}; pub(crate) use get_user_api_key::get_user_api_key; diff --git a/crates/bw/src/commands/vault.rs b/crates/bw/src/commands/vault.rs index 758be994a..f4342266a 100644 --- a/crates/bw/src/commands/vault.rs +++ b/crates/bw/src/commands/vault.rs @@ -1,6 +1,4 @@ use bitwarden::{ - auth::login::AuthSettings, - mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, vault::{CipherListView, ClientVaultExt}, Client, Error, }; @@ -22,32 +20,9 @@ pub(crate) async fn process_command( client: Client, password: Option, ) -> Result<(), Error> { - // TODO: This should be moved into the SDK - let setting = client - .platform() - .settings_repository - .get("auth") - .await - .unwrap() - .unwrap(); - let setting = serde_json::from_str::(&setting)?; - - client - .crypto() - .initialize_user_crypto(InitUserCryptoRequest { - kdf_params: setting.kdf, - email: setting.email, - private_key: setting.private_key, - method: InitUserCryptoMethod::Password { - password: password.unwrap(), - user_key: setting.user_key, - }, - }) - .await - .unwrap(); - + client.auth().unlock(password.unwrap()).await.unwrap(); match command { - VaultCommands::Get { id } => todo!(), + VaultCommands::Get { id: _ } => todo!(), VaultCommands::List {} => { let ciphers = client.vault().cipher_repository.get_all().await.unwrap(); From 8d1a64f176bf5159aa34f829cbf643d812d94293 Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 16 Aug 2024 15:01:41 +0200 Subject: [PATCH 3/5] Format --- crates/bitwarden-core/src/auth/client_auth.rs | 7 +++---- crates/bitwarden-core/src/auth/login/api_key.rs | 5 ++--- crates/bitwarden-core/src/auth/unlock.rs | 6 +++--- crates/bitwarden-core/src/platform/client_platform.rs | 5 ++--- crates/bitwarden-core/src/platform/mod.rs | 5 ++--- crates/bw/src/commands/vault.rs | 1 - 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-core/src/auth/client_auth.rs b/crates/bitwarden-core/src/auth/client_auth.rs index 0f48021ed..ec897832f 100644 --- a/crates/bitwarden-core/src/auth/client_auth.rs +++ b/crates/bitwarden-core/src/auth/client_auth.rs @@ -1,6 +1,9 @@ #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, DeviceKey, EncString, Kdf, TrustDeviceResponse}; +use super::UnlockError; +#[cfg(feature = "state")] +use super::{unlock, AuthRepository}; #[cfg(feature = "internal")] use crate::auth::login::NewAuthRequestResponse; #[cfg(feature = "secrets")] @@ -23,10 +26,6 @@ use crate::auth::{ }; use crate::{auth::renew::renew_token, error::Result, Client}; -use super::UnlockError; -#[cfg(feature = "state")] -use super::{unlock, AuthRepository}; - pub struct ClientAuth<'a> { pub(crate) client: &'a crate::Client, #[cfg(feature = "state")] diff --git a/crates/bitwarden-core/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs index 25bfea5b7..5cd00ef44 100644 --- a/crates/bitwarden-core/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -2,6 +2,8 @@ use bitwarden_crypto::{EncString, MasterKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "state")] +use crate::auth::AuthSettings; use crate::{ auth::{ api::{request::ApiTokenRequest, response::IdentityTokenResponse}, @@ -13,9 +15,6 @@ use crate::{ require, Client, }; -#[cfg(feature = "state")] -use crate::auth::AuthSettings; - pub(crate) async fn login_api_key( client: &Client, input: &ApiKeyLoginRequest, diff --git a/crates/bitwarden-core/src/auth/unlock.rs b/crates/bitwarden-core/src/auth/unlock.rs index 98fb96527..dd213fd66 100644 --- a/crates/bitwarden-core/src/auth/unlock.rs +++ b/crates/bitwarden-core/src/auth/unlock.rs @@ -1,9 +1,8 @@ use bitwarden_crypto::{CryptoError, MasterKey}; use thiserror::Error; -use crate::Client; - use super::auth_repository::AuthRepositoryError; +use crate::Client; #[derive(Debug, Error)] pub enum UnlockError { @@ -28,7 +27,8 @@ pub(crate) async fn unlock( .await? .ok_or(UnlockError::NotAuthenticated)?; - // client.internal.set_login_method(UserLoginMethod::ApiKey { client_id: (), client_secret: (), email: (), kdf: () }) + // client.internal.set_login_method(UserLoginMethod::ApiKey { client_id: (), client_secret: (), + // email: (), kdf: () }) let master_key = MasterKey::derive(&password, &settings.email, &settings.kdf)?; client.internal.initialize_user_crypto_master_key( master_key, diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs index 99df0cdd9..b3b4b1387 100644 --- a/crates/bitwarden-core/src/platform/client_platform.rs +++ b/crates/bitwarden-core/src/platform/client_platform.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "state")] +use super::settings_repository::SettingsRepository; use super::{ generate_fingerprint::{generate_fingerprint, generate_user_fingerprint}, get_user_api_key, FingerprintRequest, FingerprintResponse, SecretVerificationRequest, @@ -5,9 +7,6 @@ use super::{ }; use crate::{error::Result, Client}; -#[cfg(feature = "state")] -use super::settings_repository::SettingsRepository; - pub struct ClientPlatform<'a> { pub(crate) client: &'a Client, #[cfg(feature = "state")] diff --git a/crates/bitwarden-core/src/platform/mod.rs b/crates/bitwarden-core/src/platform/mod.rs index d7a134bc8..11889358a 100644 --- a/crates/bitwarden-core/src/platform/mod.rs +++ b/crates/bitwarden-core/src/platform/mod.rs @@ -4,10 +4,9 @@ mod get_user_api_key; mod secret_verification_request; #[cfg(feature = "state")] mod settings_repository; -#[cfg(feature = "state")] -pub use settings_repository::SettingsRepository; - pub use generate_fingerprint::{FingerprintRequest, FingerprintResponse}; pub(crate) use get_user_api_key::get_user_api_key; pub use get_user_api_key::UserApiKeyResponse; pub use secret_verification_request::SecretVerificationRequest; +#[cfg(feature = "state")] +pub use settings_repository::SettingsRepository; diff --git a/crates/bw/src/commands/vault.rs b/crates/bw/src/commands/vault.rs index f4342266a..ef730fd32 100644 --- a/crates/bw/src/commands/vault.rs +++ b/crates/bw/src/commands/vault.rs @@ -3,7 +3,6 @@ use bitwarden::{ Client, Error, }; use bitwarden_cli::Color; - use clap::Subcommand; use crate::render::{serialize_response, Output, OutputSettings, TableSerialize}; From 5c7c73612d8264dca904f6a01bc535942fdc6ebd Mon Sep 17 00:00:00 2001 From: Hinton Date: Fri, 16 Aug 2024 15:28:45 +0200 Subject: [PATCH 4/5] Fix cfg compile --- crates/bitwarden-core/src/auth/client_auth.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bitwarden-core/src/auth/client_auth.rs b/crates/bitwarden-core/src/auth/client_auth.rs index ec897832f..9a7c7b029 100644 --- a/crates/bitwarden-core/src/auth/client_auth.rs +++ b/crates/bitwarden-core/src/auth/client_auth.rs @@ -1,9 +1,8 @@ #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, DeviceKey, EncString, Kdf, TrustDeviceResponse}; -use super::UnlockError; #[cfg(feature = "state")] -use super::{unlock, AuthRepository}; +use super::{unlock, AuthRepository, UnlockError}; #[cfg(feature = "internal")] use crate::auth::login::NewAuthRequestResponse; #[cfg(feature = "secrets")] From 6ebad18e9583756b2d9614f2838b934480e36ec8 Mon Sep 17 00:00:00 2001 From: Hinton Date: Mon, 19 Aug 2024 18:45:21 +0200 Subject: [PATCH 5/5] Switch to have settings repository accept serializeable values --- .../src/auth/auth_repository.rs | 29 ++++--------------- crates/bitwarden-core/src/auth/unlock.rs | 10 ++++--- crates/bitwarden-core/src/platform/mod.rs | 2 +- .../src/platform/settings_repository.rs | 25 ++++++++++++++-- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/crates/bitwarden-core/src/auth/auth_repository.rs b/crates/bitwarden-core/src/auth/auth_repository.rs index 38506e76d..4c7444812 100644 --- a/crates/bitwarden-core/src/auth/auth_repository.rs +++ b/crates/bitwarden-core/src/auth/auth_repository.rs @@ -1,8 +1,6 @@ use bitwarden_crypto::Kdf; -use bitwarden_db::DatabaseError; -use thiserror::Error; -use crate::platform::SettingsRepository; +use crate::platform::{SettingsRepository, SettingsRepositoryError}; const SETTINGS_KEY: &str = "auth"; @@ -17,14 +15,6 @@ pub struct AuthSettings { pub private_key: String, } -#[derive(Debug, Error)] -pub enum AuthRepositoryError { - #[error(transparent)] - Database(#[from] DatabaseError), - #[error(transparent)] - Serde(#[from] serde_json::Error), -} - pub struct AuthRepository { settings_repository: SettingsRepository, } @@ -36,24 +26,15 @@ impl AuthRepository { } } - pub(crate) async fn save(&self, setting: AuthSettings) -> Result<(), AuthRepositoryError> { - let serialized = serde_json::to_string(&setting)?; - self.settings_repository - .set(SETTINGS_KEY, &serialized) - .await?; + pub(crate) async fn save(&self, setting: AuthSettings) -> Result<(), SettingsRepositoryError> { + self.settings_repository.set(SETTINGS_KEY, &setting).await?; Ok(()) } - pub(crate) async fn get(&self) -> Result, AuthRepositoryError> { + pub(crate) async fn get(&self) -> Result, SettingsRepositoryError> { let settings = self.settings_repository.get(SETTINGS_KEY).await?; - match settings { - Some(settings) => { - let settings: AuthSettings = serde_json::from_str(&settings)?; - Ok(Some(settings)) - } - None => Ok(None), - } + Ok(settings) } } diff --git a/crates/bitwarden-core/src/auth/unlock.rs b/crates/bitwarden-core/src/auth/unlock.rs index dd213fd66..3e45e6e03 100644 --- a/crates/bitwarden-core/src/auth/unlock.rs +++ b/crates/bitwarden-core/src/auth/unlock.rs @@ -1,17 +1,19 @@ use bitwarden_crypto::{CryptoError, MasterKey}; use thiserror::Error; -use super::auth_repository::AuthRepositoryError; -use crate::Client; +use crate::{platform::SettingsRepositoryError, Client}; #[derive(Debug, Error)] pub enum UnlockError { #[error(transparent)] - AuthRepository(#[from] AuthRepositoryError), + SettingRepository(#[from] SettingsRepositoryError), #[error(transparent)] Crypto(#[from] CryptoError), + #[error(transparent)] + Error(#[from] crate::Error), + #[error("The client is not authenticated or the session has expired")] NotAuthenticated, } @@ -34,7 +36,7 @@ pub(crate) async fn unlock( master_key, settings.user_key.parse()?, settings.private_key.parse()?, - ); + )?; Ok(()) } diff --git a/crates/bitwarden-core/src/platform/mod.rs b/crates/bitwarden-core/src/platform/mod.rs index 11889358a..b8c01b9c0 100644 --- a/crates/bitwarden-core/src/platform/mod.rs +++ b/crates/bitwarden-core/src/platform/mod.rs @@ -9,4 +9,4 @@ pub(crate) use get_user_api_key::get_user_api_key; pub use get_user_api_key::UserApiKeyResponse; pub use secret_verification_request::SecretVerificationRequest; #[cfg(feature = "state")] -pub use settings_repository::SettingsRepository; +pub use settings_repository::{SettingsRepository, SettingsRepositoryError}; diff --git a/crates/bitwarden-core/src/platform/settings_repository.rs b/crates/bitwarden-core/src/platform/settings_repository.rs index f04e48a3a..c445b23bb 100644 --- a/crates/bitwarden-core/src/platform/settings_repository.rs +++ b/crates/bitwarden-core/src/platform/settings_repository.rs @@ -1,8 +1,18 @@ use std::sync::Arc; use bitwarden_db::{params, Database, DatabaseError, DatabaseTrait}; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; use tokio::sync::Mutex; +#[derive(Debug, Error)] +pub enum SettingsRepositoryError { + #[error(transparent)] + Database(#[from] DatabaseError), + #[error(transparent)] + Serde(#[from] serde_json::Error), +} + pub struct SettingsRepository { db: Arc>, } @@ -12,7 +22,10 @@ impl SettingsRepository { Self { db: db.clone() } } - pub async fn get(&self, key: &str) -> Result, DatabaseError> { + pub async fn get( + &self, + key: &str, + ) -> Result, SettingsRepositoryError> { let guard = self.db.lock().await; let res = guard @@ -23,12 +36,18 @@ impl SettingsRepository { ) .await? .first() - .map(|x| x.to_owned()); + .map(|x| serde_json::from_str::(x)) + .transpose()?; Ok(res) } - pub async fn set(&self, key: &str, value: &str) -> Result<(), DatabaseError> { + pub async fn set( + &self, + key: &str, + value: &T, + ) -> Result<(), SettingsRepositoryError> { + let value = serde_json::to_string(value)?; let guard = self.db.lock().await; guard