1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
//! `passablewords` is a password validation library which checks a password against a million of //! the most common as well as it's ability to be cracked. //! //! If you're asking why use `passablewords` over `zxcvbn`, it's because `passablewords` checks //! a password against 1,000,000 of the most common passwords. `zxcvbn` only checks 30,000. //! `zxcvbn` is a great tool, however, and `passablewords` uses it to check the entropy of a given //! password to make sure it's random enough on top of being unique enough. If you are ok with the //! top 30,000 most common passwords, then you should probably use `zxcvbn`. If you want a little //! extra, consider `passablewords`. //! //! While you're free to use any of the public methods, using the `check_password` function is //! recommended since that checks for length, uniqueness, and entropy all within a single call. //! //! It's also important to note that this is provided as-is and doesn't prevent an attacker from //! gaining access to, decrypting, or guessing your user's passwords. It just makes it a little //! harder. //! //! # Example //! //! ``` //! extern crate passablewords; //! //! use passablewords::{check_password, PassablewordResult}; //! //! fn main() { //! match check_password(password) { //! Ok() => println!("That password is probably pretty good!") //! Err(err) => match(err) { //! PassablewordResult::TooShort => println!("Your password should be longer than 8 characters"), //! PassablewordResult::TooCommon => println!("Your should be more unique"), //! PassablewordResult::TooSimple => println!("Your should be more random"), //! PassablewordResult::InternalError => println! //! } //! } //! } //! ``` #[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()) }; } /// The suite of possible errors returned from passablewords. These represent the three checks made /// for length, uniqueness, and entropy. If something goes wrong during the request, an /// `InternalError` error is returned. #[derive(Debug, PartialEq)] pub enum PasswordError { /// The password is less than 8 characters and is therefore too short. TooShort, /// The password is within the list of 1,000,000 most common passwords and should not be used. TooCommon, /// The entropy of the password is too low, which means it could be easily guessable/crackable. /// A more random password should be used instead. TooSimple, /// The password isn't using ascii characters, a requirement that zxcvbn has NonAsciiPassword, /// Something went wrong during the password checks and a normal error couldn't be returned. InternalError, } /// The result type that will be returned from all public functions. It's simply a `Result` type /// that either returns `Ok` or a `PasswordError`. pub type PassablewordResult = Result<(), PasswordError>; /// Check a password to make sure it's at least 8 characters long. While this shouldn't be used as /// the only password check, it's a good baseline to start from. /// /// # Example (using rocket.rs) /// /// ``` /// match check_length(password) { /// Ok() => status::Ok /// Err(err) => match(err) { /// PassablewordResult::TooShort => status::BadRequest("Your password should be longer than 8 characters") /// } /// } /// ``` pub fn check_length(password: &str) -> PassablewordResult { if password.len() >= 8 { Ok(()) } else { Err(PasswordError::TooShort) } } /// Check a password to make sure it's not within the top million most common passwords. /// /// # Example (using rocket.rs) /// /// ``` /// match check_uniqueness(password) { /// Ok() => status::Ok /// Err(err) => match(err) { /// PassablewordResult::TooCommon => status::BadRequest("Your should be more unique") /// } /// } /// ``` pub fn check_uniqueness(password: &str) -> PassablewordResult { if PASSWORDS.contains(password) { Err(PasswordError::TooCommon) } else { Ok(()) } } /// Check a password to make sure random enough that it would take a lot of effort to crack/guess. /// This uses the awesome zxcvbn library behind the scenes. /// /// # Example (using rocket.rs) /// /// ``` /// match check_entropy(password) { /// Ok() => status::Ok /// Err(err) => match(err) { /// PassablewordResult::TooSimple => status::BadRequest("Your should be more random") /// } /// } /// ``` 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), } } } } /// Check a password's length, uniqueness, and entropy all in a single call. This is a convenience /// method and simply calls `check_length`, `check_uniqueness`, and `check_entropy` with the /// password supplied. /// /// # Example (using rocket.rs) /// /// ``` /// match check_password(password) { /// Ok() => status::Ok /// Err(err) => match(err) { /// PassablewordResult::TooShort => status::BadRequest("Your password should be longer than 8 characters"), /// PassablewordResult::TooCommon => status::BadRequest("Your should be more unique"), /// PassablewordResult::TooSimple => status::BadRequest("Your should be more random"), /// PassablewordResult::InternalError => status::InternalServerError /// } /// } /// ``` 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(())); } }