blob: 08198d740a480db4067d5086cdd8f0bf25761227 [file] [log] [blame]
#include <zxcvbn/feedback.hpp>
#include <zxcvbn/frequency_lists.hpp>
#include <zxcvbn/optional.hpp>
#include <zxcvbn/scoring.hpp>
#include <zxcvbn/util.hpp>
#include <regex>
namespace zxcvbn {
static
optional::optional<Feedback> get_match_feedback(const Match & match, bool is_sole_match);
static
Feedback get_dictionary_match_feedback(const Match & match, bool is_sole_match);
Feedback get_feedback(score_t score,
const std::vector<Match> & sequence) {
// starting feedback
if (!sequence.size()) {
return {
"",
{
"Use a few words, avoid common phrases",
"No need for symbols, digits, or uppercase letters",
},
};
}
// no feedback if score is good or great.
if (score > 2) return {"", {}};
// tie feedback to the longest match for longer sequences
auto longest_match = sequence.begin();
for (auto match = longest_match + 1; match != sequence.end(); ++match) {
if (match->token.length() > longest_match->token.length()) {
longest_match = match;
}
}
auto maybe_feedback = get_match_feedback(*longest_match, sequence.size() == 1);
auto extra_feedback = "Add another word or two. Uncommon words are better.";
if (maybe_feedback) {
auto & feedback = *maybe_feedback;
feedback.suggestions.insert(maybe_feedback->suggestions.begin(),
extra_feedback);
return feedback;
}
else {
return {"", {extra_feedback}};
}
}
optional::optional<Feedback> get_match_feedback(const Match & match_, bool is_sole_match) {
switch (match_.get_pattern()) {
case MatchPattern::DICTIONARY: {
return get_dictionary_match_feedback(match_, is_sole_match);
}
case MatchPattern::SPATIAL: {
auto & match = match_.get_spatial();
auto warning = (match.turns == 1)
? "Straight rows of keys are easy to guess"
: "Short keyboard patterns are easy to guess";
return Feedback{warning, {
"Use a longer keyboard pattern with more turns",
}};
}
case MatchPattern::REPEAT: {
auto warning = (match_.get_repeat().base_token.length() == 1)
? "Repeats like \"aaa\" are easy to guess"
: "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"";
return Feedback{warning, {
"Avoid repeated words and characters",
}};
}
case MatchPattern::SEQUENCE: {
return Feedback{"Sequences like abc or 6543 are easy to guess",
{"Avoid sequences"},
};
}
case MatchPattern::REGEX: {
auto & match = match_.get_regex();
if (match.regex_tag == RegexTag::RECENT_YEAR) {
return Feedback{"Recent years are easy to guess", {
"Avoid recent years",
"Avoid years that are associated with you",
}};
}
break;
}
case MatchPattern::DATE: {
return Feedback{"Dates are often easy to guess", {
"Avoid dates and years that are associated with you",
}};
}
default:
break;
}
return optional::nullopt;
}
static
Feedback get_dictionary_match_feedback(const Match & match_, bool is_sole_match) {
assert(match_.get_pattern() == MatchPattern::DICTIONARY);
auto & match = match_.get_dictionary();
auto warning = [&] {
if (match.dictionary_tag == DictionaryTag::PASSWORDS) {
if (is_sole_match and !match.l33t and !match.reversed) {
if (match.rank <= 10) {
return "This is a top-10 common password";
}
else if (match.rank <= 100) {
return "This is a top-100 common password";
}
else {
return "This is a very common password";
}
}
else if (match_.guesses_log10 <= 4) {
return "This is similar to a commonly used password";
}
}
else if (match.dictionary_tag == DictionaryTag::ENGLISH_WIKIPEDIA) {
if (is_sole_match) {
return "A word by itself is easy to guess";
}
}
else if (match.dictionary_tag == DictionaryTag::SURNAMES ||
match.dictionary_tag == DictionaryTag::MALE_NAMES ||
match.dictionary_tag == DictionaryTag::FEMALE_NAMES) {
if (is_sole_match) {
return "Names and surnames by themselves are easy to guess";
}
else {
return "Common names and surnames are easy to guess";
}
}
return "";
}();
std::vector<std::string> suggestions;
auto & word = match_.token;
if (std::regex_search(word, START_UPPER())) {
suggestions.push_back("Capitalization doesn't help very much");
} else if (std::regex_search(word, ALL_UPPER()) and
// XXX: UTF-8
util::ascii_lower(word) == word) {
suggestions.push_back("All-uppercase is almost as easy to guess as all-lowercase");
}
if (match.reversed and match_.token.length() >= 4) {
suggestions.push_back("Reversed words aren't much harder to guess");
}
if (match.l33t) {
suggestions.push_back("Predictable substitutions like '@' instead of 'a' don't help very much");
}
return {warning, suggestions};
}
}