use matching::Match;
use matching::patterns::*;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "ser", derive(Serialize))]
pub struct Feedback {
pub warning: Option<&'static str>,
pub suggestions: Vec<&'static str>,
}
#[doc(hidden)]
pub fn get_feedback(score: u8, sequence: &[Match]) -> Option<Feedback> {
if sequence.is_empty() {
return Some(Feedback {
warning: None,
suggestions: vec![
"Use a few words, avoid common phrases.",
"No need for symbols, digits, or uppercase letters.",
],
});
}
if score >= 3 {
return None;
}
let longest_match = sequence
.iter()
.max_by_key(|x| x.token.chars().count())
.unwrap();
let mut feedback = get_match_feedback(longest_match, sequence.len() == 1);
let extra_feedback = "Add another word or two. Uncommon words are better.";
feedback.suggestions.insert(0, extra_feedback);
Some(feedback)
}
fn get_match_feedback(cur_match: &Match, is_sole_match: bool) -> Feedback {
match cur_match.pattern {
MatchPattern::Dictionary(ref pattern) => {
get_dictionary_match_feedback(cur_match, pattern, is_sole_match)
}
MatchPattern::Spatial(ref pattern) => Feedback {
warning: Some(if pattern.turns == 1 {
"Straight rows of keys are easy to guess."
} else {
"Short keyboard patterns are easy to guess."
}),
suggestions: vec!["Use a longer keyboard pattern with more turns."],
},
MatchPattern::Repeat(ref pattern) => Feedback {
warning: Some(if pattern.base_token.chars().count() == 1 {
"Repeats like \"aaa\" are easy to guess."
} else {
"Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"."
}),
suggestions: vec!["Avoid repeated words and characters."],
},
MatchPattern::Sequence(_) => Feedback {
warning: Some("Sequences like abc or 6543 are easy to guess."),
suggestions: vec!["Avoid sequences."],
},
MatchPattern::Regex(ref pattern) => {
if pattern.regex_name == "recent_year" {
Feedback {
warning: Some("Recent years are easy to guess."),
suggestions: vec![
"Avoid recent years.",
"Avoid years that are associated with you.",
],
}
} else {
Feedback::default()
}
}
MatchPattern::Date(_) => Feedback {
warning: Some("Dates are often easy to guess."),
suggestions: vec!["Avoid dates and years that are associated with you."],
},
_ => Feedback {
warning: None,
suggestions: vec![],
},
}
}
fn get_dictionary_match_feedback(
cur_match: &Match,
pattern: &DictionaryPattern,
is_sole_match: bool,
) -> Feedback {
let warning = match pattern.dictionary_name {
"passwords" => Some(if is_sole_match && !pattern.l33t && !pattern.reversed {
let rank = pattern.rank;
if rank <= 10 {
"This is a top-10 common password."
} else if rank <= 100 {
"This is a top-100 common password."
} else {
"This is a very common password."
}
} else {
"This is similar to a commonly used password."
}),
"english" => {
if is_sole_match {
Some("A word by itself is easy to guess.")
} else {
None
}
}
"surnames" | "female_names" | "male_names" => Some(if is_sole_match {
"Names and surnames by themselves are easy to guess."
} else {
"Common names and surnames are easy to guess."
}),
_ => None,
};
let mut suggestions = Vec::new();
let word = &cur_match.token;
if word.is_empty() {
return Feedback::default();
}
if word.chars().next().unwrap().is_uppercase() {
suggestions.push("Capitalization doesn't help very much.");
} else if word.chars().all(char::is_uppercase) {
suggestions.push("All-uppercase is almost as easy to guess as all-lowercase.");
}
if pattern.reversed && word.chars().count() >= 4 {
suggestions.push("Reversed words aren't much harder to guess.");
}
if pattern.l33t {
suggestions.push("Predictable substitutions like '@' instead of 'a' don't help very much.");
}
Feedback {
warning: warning,
suggestions: suggestions,
}
}