Skip to content

Commit

Permalink
Showcase how state can be used in the cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Aug 15, 2024
1 parent 8bcd963 commit acf0298
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 16 deletions.
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions crates/bitwarden-core/src/auth/login/api_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions crates/bitwarden-core/src/auth/login/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,15 @@ pub(crate) fn parse_prelogin(response: PreloginResponseModel) -> Result<Kdf> {
},
})
}

#[cfg(feature = "state")]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct AuthSettings {
pub email: String,
pub token: String,
pub refresh_token: Option<String>,
pub kdf: Kdf,

pub user_key: String,
pub private_key: String,
}
11 changes: 10 additions & 1 deletion crates/bitwarden-core/src/platform/client_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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()),
}
}
}
2 changes: 2 additions & 0 deletions crates/bitwarden-core/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions crates/bitwarden-core/src/platform/settings_repository.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::sync::Arc;

use bitwarden_db::{params, Database, DatabaseError, DatabaseTrait};
use tokio::sync::Mutex;

pub struct SettingsRepository {
db: Arc<Mutex<Database>>,
}

impl SettingsRepository {
pub fn new(db: Arc<Mutex<Database>>) -> Self {
Self { db: db.clone() }
}

pub async fn get(&self, key: &str) -> Result<Option<String>, DatabaseError> {
let guard = self.db.lock().await;

let res = guard
.query_map(
"SELECT value FROM settings WHERE key = ?1",
[key],
|row| -> Result<String, _> { 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(())
}
}
13 changes: 12 additions & 1 deletion crates/bw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions crates/bw/src/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
1 change: 1 addition & 0 deletions crates/bw/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod vault;
77 changes: 77 additions & 0 deletions crates/bw/src/commands/vault.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
) -> 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::<AuthSettings>(&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()]]
}
}
19 changes: 10 additions & 9 deletions crates/bw/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String>,
},

#[command(long_about = "Pull the latest vault data from the server")]
Expand Down Expand Up @@ -85,12 +90,6 @@ enum LoginCommands {
},
}

#[derive(Subcommand, Clone)]
enum ItemCommands {
Get { id: String },
Create {},
}

#[derive(Subcommand, Clone)]
enum GeneratorCommands {
Password(PasswordGeneratorArgs),
Expand Down Expand Up @@ -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) => {
Expand Down
Loading

0 comments on commit acf0298

Please sign in to comment.