Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
465 changes: 458 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion crates/bw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@ repository.workspace = true
license-file.workspace = true

[dependencies]
base64 = ">=0.22.1, <0.23"
bat = { version = "0.25.0", features = [
"regex-fancy",
], default-features = false }
bitwarden-cli = { workspace = true }
bitwarden-core = { workspace = true }
bitwarden-generators = { workspace = true }
bitwarden-pm = { workspace = true }
bitwarden-vault = { workspace = true }
clap = { version = "4.5.4", features = ["derive", "env"] }
clap_complete = "4.5.55"
color-eyre = "0.6.3"
env_logger = "0.11.1"
inquire = "0.7.0"
erased-serde = "0.4.6"
inquire = "0.9.1"
log = "0.4.20"
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = "0.9.33"
tokio = { workspace = true, features = ["rt-multi-thread"] }

[lints]
Expand Down
9 changes: 9 additions & 0 deletions crates/bw/src/admin_console/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use clap::Subcommand;

#[derive(Subcommand, Clone)]
pub enum ConfirmCommand {
OrgMember {
#[arg(long, help = "Organization id for an organization object.")]
organizationid: String,
},
}
94 changes: 93 additions & 1 deletion crates/bw/src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,94 @@
use bitwarden_cli::text_prompt_when_none;
use bitwarden_core::ClientSettings;
use clap::{Args, Subcommand};

mod login;
pub(crate) use login::{login_api_key, login_device, login_password};
use inquire::Password;

use crate::render::CommandResult;

// TODO(CLI): This is incompatible with the current node CLI
#[derive(Args, Clone)]
pub struct LoginArgs {
#[command(subcommand)]
pub command: LoginCommands,

#[arg(short = 's', long, global = true, help = "Server URL")]
pub server: Option<String>,
}

#[derive(Subcommand, Clone)]
pub enum LoginCommands {
Password {
#[arg(short = 'e', long, help = "Email address")]
email: Option<String>,
},
ApiKey {
client_id: Option<String>,
client_secret: Option<String>,
},
Device {
#[arg(short = 'e', long, help = "Email address")]
email: Option<String>,
device_identifier: Option<String>,
},
}

impl LoginArgs {
pub async fn run(self) -> CommandResult {
let settings = self.server.map(|server| ClientSettings {
api_url: format!("{server}/api"),
identity_url: format!("{server}/identity"),
..Default::default()
});
let client = bitwarden_core::Client::new(settings);

match self.command {
// FIXME: Rust CLI will not support password login!
LoginCommands::Password { email } => {
login::login_password(client, email).await?;
}
LoginCommands::ApiKey {
client_id,
client_secret,
} => login::login_api_key(client, client_id, client_secret).await?,
LoginCommands::Device {
email,
device_identifier,
} => {
login::login_device(client, email, device_identifier).await?;
}
}
Ok("Successfully logged in!".into())
}
}

#[derive(Args, Clone)]
pub struct RegisterArgs {
#[arg(short = 'e', long, help = "Email address")]
email: Option<String>,

name: Option<String>,

password_hint: Option<String>,

#[arg(short = 's', long, global = true, help = "Server URL")]
server: Option<String>,
}

impl RegisterArgs {
#[allow(unused_variables, clippy::unused_async)]
pub async fn run(self) -> CommandResult {
let settings = self.server.map(|server| ClientSettings {
api_url: format!("{server}/api"),
identity_url: format!("{server}/identity"),
..Default::default()
});
let client = bitwarden_core::Client::new(settings);

let email = text_prompt_when_none("Email", self.email)?;
let password = Password::new("Password").prompt()?;

unimplemented!("Registration is not yet implemented");
}
}
215 changes: 215 additions & 0 deletions crates/bw/src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use bitwarden_cli::Color;
use clap::{Args, Parser, Subcommand};

use crate::{
admin_console::ConfirmCommand,
auth::{LoginArgs, RegisterArgs},
platform::ConfigCommand,
render::Output,
tools::GenerateArgs,
vault::{ItemCommands, TemplateCommands},
};

pub const SESSION_ENV: &str = "BW_SESSION";

#[derive(Parser, Clone)]
#[command(name = "Bitwarden CLI", version, about = "Bitwarden CLI", long_about = None, disable_version_flag = true)]
pub struct Cli {
// Optional as a workaround for https://github.com/clap-rs/clap/issues/3572
#[command(subcommand)]
pub command: Option<Commands>,

#[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON)]
pub output: Output,

#[arg(short = 'c', long, global = true, value_enum, default_value_t = Color::Auto)]
pub color: Color,

// TODO(CLI): Pretty/raw/response options
#[arg(
long,
global = true,
env = SESSION_ENV,
help = "The session key used to decrypt your vault data. Can be obtained with `bw login` or `bw unlock`."
)]
pub session: Option<String>,

#[arg(
long,
global = true,
help = "Exit with a success exit code (0) unless an error is thrown."
)]
pub cleanexit: bool,

#[arg(
short = 'q',
long,
global = true,
help = "Don't return anything to stdout."
)]
pub quiet: bool,

#[arg(
long,
global = true,
help = "Do not prompt for interactive user input."
)]
pub nointeraction: bool,

// Clap uses uppercase V for the short flag by default, but we want lowercase v
// for compatibility with the node CLI:
// https://github.com/clap-rs/clap/issues/138
#[arg(short = 'v', long, action = clap::builder::ArgAction::Version)]
pub version: (),
}

#[derive(Subcommand, Clone)]
pub enum Commands {
// Auth commands
#[command(long_about = "Log into a user account.")]
Login(LoginArgs),

#[command(long_about = "Log out of the current user account.")]
Logout,

#[command(long_about = "Register a new user account.")]
Register(RegisterArgs),

// KM commands
#[command(long_about = "Unlock the vault and return a session key.")]
Unlock(UnlockArgs),

// Platform commands
#[command(long_about = "Pull the latest vault data from server.")]
Sync {
#[arg(short = 'f', long, help = "Force a full sync.")]
force: bool,

#[arg(long, help = "Get the last sync date.")]
last: bool,
},

#[command(long_about = "Base 64 encode stdin.")]
Encode,

#[command(long_about = "Configure CLI settings.")]
Config {
#[command(subcommand)]
command: ConfigCommand,
},

#[command(long_about = "Check for updates.")]
Update {
#[arg(long, help = "Return only the download URL for the update.")]
raw: bool,
},

#[command(long_about = "Generate shell completions.")]
Completion {
#[arg(long, help = "The shell to generate completions for.")]
shell: Option<clap_complete::Shell>,
},

#[command(
long_about = "Show server, last sync, user information, and vault status.",
after_help = r#"Example return value:
{
"serverUrl": "https://bitwarden.example.com",
"lastSync": "2020-06-16T06:33:51.419Z",
"userEmail": "user@example.com",
"userId": "00000000-0000-0000-0000-000000000000",
"status": "locked"
}

Notes:
`status` is one of:
- `unauthenticated` when you are not logged in
- `locked` when you are logged in and the vault is locked
- `unlocked` when you are logged in and the vault is unlocked
"#
)]
Status,

// Vault commands
#[command(long_about = "Manage vault objects.")]
Item {
#[command(subcommand)]
command: ItemCommands,
},
#[command(long_about = "Get the available templates")]
Template {
#[command(subcommand)]
command: TemplateCommands,
},

// These are the old style action-name commands, to be replaced by name-action commands in the
// future
#[command(long_about = "List an array of objects from the vault.")]
List,
#[command(long_about = "Get an object from the vault.")]
Get,
#[command(long_about = "Create an object in the vault.")]
Create,
#[command(long_about = "Edit an object from the vault.")]
Edit,
#[command(long_about = "Delete an object from the vault.")]
Delete,
#[command(long_about = "Restores an object from the trash.")]
Restore,
#[command(long_about = "Move an item to an organization.")]
Move,

// Admin console commands
#[command(long_about = "Confirm an object to the organization.")]
Confirm {
#[command(subcommand)]
command: ConfirmCommand,
},

// Tools commands
#[command(long_about = "Generate a password/passphrase.")]
#[command(after_help = r#"Notes:
Default options are `-uln --length 14`.
Minimum `length` is 5.
Minimum `words` is 3.

Examples:
bw generate
bw generate -u -l --length 18
bw generate -ulns --length 25
bw generate -ul
bw generate -p --separator _
bw generate -p --words 5 --separator space
bw generate -p --words 5 --separator empty
"#)]
Generate(GenerateArgs),
#[command(long_about = "Import vault data from a file.")]
Import,
#[command(long_about = "Export vault data to a CSV, JSON or ZIP file.")]
Export,
#[command(long_about = "--DEPRECATED-- Move an item to an organization.")]
Share,
#[command(
long_about = "Work with Bitwarden sends. A Send can be quickly created using this command or subcommands can be used to fine-tune the Send."
)]
Send,
#[command(long_about = "Access a Bitwarden Send from a url.")]
Receive,
}

#[derive(Args, Clone)]
pub struct UnlockArgs {
pub password: Option<String>,

#[arg(long, help = "Environment variable storing your password.")]
pub passwordenv: Option<String>,

#[arg(
long,
help = "Path to a file containing your password as its first line."
)]
pub passwordfile: Option<String>,

#[arg(long, help = "Only return the session key.")]
pub raw: bool,
}
Loading
Loading