#[macro_use]
extern crate lazy_static;
extern crate zxcvbn;
use std::fs::File;
use std::io::prelude::*;
use std::collections::HashSet;
use std::iter::FromIterator;
use zxcvbn::{zxcvbn, ZxcvbnError};
lazy_static! {
static ref FILE_CONTENTS: String = {
let mut f = File::open("src/common-passwords.txt")
.expect("There was a problem opening the list of passwords");
let mut file_contents = String::new();
f.read_to_string(&mut file_contents)
.expect("There was a problem reading the common passwords file");
file_contents
};
static ref PASSWORDS: HashSet<&'static str> = {
HashSet::from_iter(FILE_CONTENTS.lines())
};
}
#[derive(Debug, PartialEq)]
pub enum PasswordError {
TooShort,
TooCommon,
TooSimple,
NonAsciiPassword,
InternalError,
}
pub type PassablewordResult = Result<(), PasswordError>;
pub fn check_length(password: &str) -> PassablewordResult {
if password.len() >= 8 {
Ok(())
} else {
Err(PasswordError::TooShort)
}
}
pub fn check_uniqueness(password: &str) -> PassablewordResult {
if PASSWORDS.contains(password) {
Err(PasswordError::TooCommon)
} else {
Ok(())
}
}
pub fn check_entropy(password: &str) -> PassablewordResult {
match zxcvbn(password, &[]) {
Ok(result) => {
if result.score >= 3 {
Ok(())
} else {
Err(PasswordError::TooSimple)
}
}
Err(zxcvbn_error) => {
match zxcvbn_error {
ZxcvbnError::NonAsciiPassword => Err(PasswordError::NonAsciiPassword),
_ => Err(PasswordError::InternalError),
}
}
}
}
pub fn check_password(password: &str) -> PassablewordResult {
check_length(password).and(check_uniqueness(password)).and(
check_entropy(password),
)
}
#[cfg(test)]
mod tests {
use super::{check_entropy, check_length, check_password, check_uniqueness, PasswordError};
#[test]
fn it_validates_length() {
let too_short = check_length("short");
let long_enough = check_length("this is a long password");
assert_eq!(too_short, Err(PasswordError::TooShort));
assert_eq!(long_enough, Ok(()));
}
#[test]
fn it_validates_uniqueness() {
let too_common = check_uniqueness("password");
let unique_enough = check_uniqueness("this is a unique password");
assert_eq!(too_common, Err(PasswordError::TooCommon));
assert_eq!(unique_enough, Ok(()));
}
#[test]
fn it_validates_entropy() {
let too_simple = check_entropy("NotTooRandom");
let random_enough = check_entropy("Th1s iS a Sup3rR4ndom PassW0rd!");
assert_eq!(too_simple, Err(PasswordError::TooSimple));
assert_eq!(random_enough, Ok(()));
}
#[test]
fn it_validates_a_password() {
let too_short = check_password("short");
let too_common = check_password("password");
let too_simple = check_password("NotTooRandom");
let ok_password = check_password("Th1s iS a Sup3rR4ndom PassW0rd!");
assert_eq!(too_short, Err(PasswordError::TooShort));
assert_eq!(too_common, Err(PasswordError::TooCommon));
assert_eq!(too_simple, Err(PasswordError::TooSimple));
assert_eq!(ok_password, Ok(()));
}
}