deadbolt_parser/
password.rs

1use deadbolt_crypto::rand::Random;
2use regex::Regex;
3use secrecy::Secret;
4
5const MIN_PASSWORD_LEN: usize = 18;
6
7fn is_no_repetition(password: &str) -> bool {
8    // https://stackoverflow.com/questions/63327968/detect-duplicated-elements-of-a-string-slice-happening-in-order
9    let n = 2;
10    let mut repetitions = password
11        .chars()
12        .map(Some)
13        .chain(std::iter::once(None))
14        .scan((0, None), |(count, ch), v| match ch {
15            Some(c) if *c == v => {
16                *count += 1;
17                Some((None, *count))
18            }
19            _ => Some((ch.replace(v), std::mem::replace(count, 1))),
20        })
21        .filter_map(|(ch, count)| match ch {
22            Some(Some(ch)) if count >= n => Some((ch, count)),
23            _ => None,
24        })
25        .peekable();
26
27    repetitions.peek().is_none()
28}
29
30fn is_good_distribution(password: &str, punctuation: Option<bool>) -> bool {
31    let uppercase_regex = Regex::new(r"[A-Z]").unwrap();
32    let lowercase_regex = Regex::new(r"[a-z]").unwrap();
33    let digit_regex = Regex::new(r"\d").unwrap();
34    let punctuation_regex = Regex::new(r"[~!@#\$%\^&\*()-_=\+\[\]\{\};':,\./<>?]").unwrap();
35
36    if uppercase_regex.is_match(password)
37        && lowercase_regex.is_match(password)
38        && digit_regex.is_match(password)
39    {
40        if let Some(punctuation) = punctuation {
41            if punctuation {
42                return punctuation_regex.is_match(password);
43            }
44        }
45        return true;
46    }
47    false
48}
49
50fn is_strong_password(password: &str, punctuation: Option<bool>) -> bool {
51    if !is_no_repetition(password) {
52        return false;
53    }
54    if !is_good_distribution(password, punctuation) {
55        return false;
56    }
57    true
58}
59
60pub fn get_strong_random_password(
61    length: Option<usize>,
62    punctuation: Option<bool>,
63) -> Secret<String> {
64    let mut password = Random::get_rand_chars(
65        length.unwrap_or(MIN_PASSWORD_LEN),
66        punctuation.unwrap_or(true),
67    );
68    loop {
69        if is_strong_password(&password, punctuation) {
70            break;
71        }
72        password = Random::get_rand_chars(
73            length.unwrap_or(MIN_PASSWORD_LEN),
74            punctuation.unwrap_or(true),
75        );
76    }
77
78    Secret::new(password)
79}