bitwarden_core/auth/password/
strength.rs1use zxcvbn::zxcvbn;
2
3const GLOBAL_INPUTS: [&str; 3] = ["bitwarden", "bit", "warden"];
4
5pub(crate) fn password_strength(
6 password: String,
7 email: String,
8 additional_inputs: Vec<String>,
9) -> u8 {
10 let mut inputs = email_to_user_inputs(&email);
11 inputs.extend(additional_inputs);
12
13 let mut arr: Vec<_> = inputs.iter().map(String::as_str).collect();
14 arr.extend(GLOBAL_INPUTS);
15
16 zxcvbn(&password, &arr).score().into()
17}
18
19fn email_to_user_inputs(email: &str) -> Vec<String> {
20 let parts = email.split_once('@');
21 match parts {
22 Some((prefix, _)) => prefix
23 .trim()
24 .to_lowercase()
25 .split(|c: char| !c.is_alphanumeric())
26 .map(str::to_owned)
27 .collect(),
28 None => vec![],
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::{email_to_user_inputs, password_strength};
35
36 #[test]
37 fn test_password_strength() {
38 let cases = vec![
39 ("password", "[email protected]", 0),
40 ("password11", "[email protected]", 1),
41 ("Weakpass2", "[email protected]", 2),
42 ("GoodPass3!", "[email protected]", 3),
43 ("VeryStrong123@#", "[email protected]", 4),
44 ];
45
46 for (password, email, expected) in cases {
47 let result = password_strength(password.to_owned(), email.to_owned(), vec![]);
48 assert_eq!(expected, result, "{email}: {password}");
49 }
50 }
51
52 #[test]
53 fn test_penalize_email() {
54 let password = "asdfjkhkjwer!";
55
56 let result = password_strength(
57 password.to_owned(),
58 "[email protected]".to_owned(),
59 vec![],
60 );
61 assert_eq!(result, 4);
62
63 let result = password_strength(
64 password.to_owned(),
65 "[email protected]".to_owned(),
66 vec![],
67 );
68 assert_eq!(result, 1);
69 }
70
71 #[test]
72 fn test_email_to_user_inputs() {
73 let email = "[email protected]";
74 let result = email_to_user_inputs(email);
75
76 assert_eq!(result, vec!["random"]);
77 }
78}