From 23869052a1fb2aaf95ed4e535d207c04b1ae09fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Fri, 29 Sep 2023 20:32:54 +0200 Subject: [PATCH] [PM-3435] Passphrase generator --- .../bitwarden/src/tool/generators/password.rs | 53 +++++++++++++++++-- crates/bw/src/main.rs | 32 +++++++++-- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs index 0a38740827..feb837a08c 100644 --- a/crates/bitwarden/src/tool/generators/password.rs +++ b/crates/bitwarden/src/tool/generators/password.rs @@ -1,4 +1,5 @@ -use crate::error::Result; +use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST}; +use rand::{seq::SliceRandom, Rng}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -44,6 +45,52 @@ pub(super) fn password(_input: PasswordGeneratorRequest) -> Result { Ok("pa11w0rd".to_string()) } -pub(super) fn passphrase(_input: PassphraseGeneratorRequest) -> Result { - Ok("correct-horse-battery-staple".to_string()) +const DEFAULT_PASSPHRASE_NUM_WORDS: u8 = 3; +const DEFAULT_PASSPHRASE_SEPARATOR: char = ' '; + +pub(super) fn passphrase(input: PassphraseGeneratorRequest) -> Result { + let words = input.num_words.unwrap_or(DEFAULT_PASSPHRASE_NUM_WORDS); + let separator = input + .word_separator + .map(|s| s.chars().next()) + .flatten() + .unwrap_or(DEFAULT_PASSPHRASE_SEPARATOR); + + let capitalize = input.capitalize.unwrap_or(false); + let include_number = input.include_number.unwrap_or(false); + + fn capitalize_first_letter(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } + } + + let mut rand = rand::thread_rng(); + + let insert_number_idx = include_number.then(|| rand.gen_range(0..words)); + let mut passphrase = String::new(); + + for idx in 0..words { + let word = EFF_LONG_WORD_LIST + .choose(&mut rand) + .expect("slice is not empty"); + + if capitalize { + passphrase.push_str(&capitalize_first_letter(word)); + } else { + passphrase.push_str(word); + } + + if insert_number_idx == Some(idx) { + passphrase.push_str(&rand.gen_range(0..=9).to_string()); + } + + if idx != words - 1 { + passphrase.push(separator) + } + } + + Ok(passphrase) } diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index 73e64a4aa0..992072d547 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -1,5 +1,7 @@ use bitwarden::{ - auth::RegisterRequest, client::client_settings::ClientSettings, tool::PasswordGeneratorRequest, + auth::RegisterRequest, + client::client_settings::ClientSettings, + tool::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, }; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; use clap::{command, Args, CommandFactory, Parser, Subcommand}; @@ -87,7 +89,7 @@ enum ItemCommands { #[derive(Subcommand, Clone)] enum GeneratorCommands { Password(PasswordGeneratorArgs), - Passphrase {}, + Passphrase(PassphraseGeneratorArgs), } #[derive(Args, Clone)] @@ -113,6 +115,18 @@ struct PasswordGeneratorArgs { length: u8, } +#[derive(Args, Clone)] +struct PassphraseGeneratorArgs { + #[arg(long, default_value = "3", help = "Number of words in the passphrase")] + words: u8, + #[arg(long, default_value = " ", help = "Separator between words")] + separator: char, + #[arg(long, action, help = "Capitalize the first letter of each word")] + capitalize: bool, + #[arg(long, action, help = "Include a number in one of the words")] + include_number: bool, +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -206,7 +220,19 @@ async fn process_commands() -> Result<()> { println!("{}", password); } - GeneratorCommands::Passphrase {} => todo!(), + GeneratorCommands::Passphrase(args) => { + let passphrase = client + .generator() + .passphrase(PassphraseGeneratorRequest { + num_words: Some(args.words), + word_separator: Some(args.separator.to_string()), + capitalize: Some(args.capitalize), + include_number: Some(args.include_number), + }) + .await?; + + println!("{}", passphrase); + } }, };