use crate::io::CliInputOutput;
use crate::io::OutputType;
use rand::RngExt;
use rtoolbox::safe_string::SafeString;
use std::io::Result as IoResult;
fn generate_password(alnum: bool, len: usize) -> IoResult<SafeString> {
let mut password_as_string = String::new();
let mut rng = rand::rng();
for _ in 0..len {
if alnum {
match rng.random_range(0..3) {
0 => password_as_string.push(rng.random_range(48..58) as u8 as char),
1 => password_as_string.push(rng.random_range(65..91) as u8 as char),
2 => password_as_string.push(rng.random_range(97..123) as u8 as char),
_ => unreachable!(),
}
} else {
password_as_string.push(rng.random_range(33..127) as u8 as char);
}
}
Ok(SafeString::from_string(password_as_string))
}
fn password_is_hard(password: &str, alnum: bool) -> bool {
let is_punctuation = |c| -> bool { "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".find(c).is_some() };
password.find(char::is_numeric).is_some()
&& password.find(char::is_lowercase).is_some()
&& password.find(char::is_uppercase).is_some()
&& (alnum || password.find(is_punctuation).is_some())
}
pub struct PasswordSpec {
pub alnum: bool,
pub len: usize,
}
impl PasswordSpec {
pub fn new(alnum: bool, password_len: Option<usize>) -> PasswordSpec {
PasswordSpec {
alnum,
len: password_len.unwrap_or(32),
}
}
pub fn generate_hard_password(&self) -> IoResult<SafeString> {
loop {
let password = generate_password(self.alnum, self.len)?;
if password_is_hard(password.as_ref(), self.alnum) {
return Ok(password);
}
}
}
}
pub fn check_password_len(len: usize, io: &mut impl CliInputOutput) -> Option<usize> {
if len < 4 {
io.error("Woops! The length of the password must be at least 4. This allows us to make sure your password is secure.", OutputType::Error);
None
} else {
Some(len)
}
}
#[cfg(test)]
mod test {
use crate::generate::PasswordSpec;
use std::ops::Deref;
#[test]
fn test_default_password_size_is_32() {
assert_eq!(
PasswordSpec::new(false, None)
.generate_hard_password()
.unwrap()
.len(),
32
);
assert_eq!(
PasswordSpec::new(false, Some(16))
.generate_hard_password()
.unwrap()
.len(),
16
);
}
#[test]
fn test_generate_password_alnum() {
let ps = PasswordSpec::new(true, None);
let pw = ps.generate_hard_password().unwrap();
for c in pw.deref().chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => {}
_ => panic!(),
}
}
let ps = PasswordSpec::new(false, None);
let pw = ps.generate_hard_password().unwrap();
let mut ok = false;
for c in pw.deref().chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => {}
_ => ok = true,
}
}
assert!(ok);
}
}