rust_unique_pass/password/
password_generation.rs1use crate::core::app_errors::{GenerationError, Result};
16use crate::crypto::rng::SecureRng;
17use crate::password::password_length::validate_password_length;
18use rand::prelude::IndexedRandom;
19use rand::prelude::SliceRandom;
20use zeroize::Zeroizing;
21use zxcvbn::{Score, zxcvbn};
22
23const MAX_GENERATION_ATTEMPTS: usize = 500000;
24const STRENGTH_CHECK_INTERVAL: usize = 10;
25
26#[doc(alias = "generate")]
37#[doc(alias = "password")]
38#[doc(alias = "secure")]
39pub async fn produce_secure_password(
40 all_vec: &[char],
41 len: usize,
42 req: &[Vec<char>],
43) -> Result<Zeroizing<String>> {
44 validate_password_length(len)?;
45
46 if all_vec.is_empty() {
48 return Err(GenerationError::GenerationFailed);
49 }
50 if req.len() > len {
51 return Err(GenerationError::InvalidLength);
52 }
53
54 let mut candidates = Vec::with_capacity(STRENGTH_CHECK_INTERVAL);
55
56 for attempt in 1..=MAX_GENERATION_ATTEMPTS {
57 if let Some(pwd) = assemble_random_password(all_vec, len, req).await {
58 candidates.push(pwd);
59
60 if attempt % STRENGTH_CHECK_INTERVAL == 0 || attempt == MAX_GENERATION_ATTEMPTS {
62 for candidate in candidates.drain(..) {
63 if is_strong(&candidate) {
64 return Ok(Zeroizing::new(candidate));
65 }
66 }
67 }
68 }
69
70 if attempt % 10_000 == 0 {}
72 }
73
74 Err(GenerationError::GenerationFailed)
75}
76
77pub async fn assemble_random_password(
78 all_vec: &[char],
79 len: usize,
80 req: &[Vec<char>],
81) -> Option<String> {
82 if all_vec.is_empty() {
83 return None;
84 }
85 let mut rng = match SecureRng::new() {
86 Ok(rng) => rng,
87 Err(_) => return None,
89 };
90
91 let need: Vec<char> = req
93 .iter()
94 .filter_map(|set| set.choose(&mut rng).copied())
95 .collect();
96
97 if need.len() > len {
98 return None;
99 }
100
101 let rest = len - need.len();
102 let mut pwd: Vec<char> = need
103 .into_iter()
104 .chain((0..rest).filter_map(|_| all_vec.choose(&mut rng).copied()))
105 .collect();
106
107 pwd.shuffle(&mut rng);
108 Some(pwd.iter().collect())
109}
110
111fn is_strong(pwd: &str) -> bool {
121 zxcvbn(pwd, &[]).score() == Score::Four
122}