password length

This commit is contained in:
Aravinth Manivannan 2021-03-04 20:41:50 +05:30
parent 9fbfd359b3
commit 769bf21c61
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
4 changed files with 106 additions and 36 deletions

View file

@ -1,14 +1,19 @@
//To gain fine-grained control over how credentials are managed, consider using ConfigBuilder:
use argon2_creds::{Config, ConfigBuilder};
use argon2_creds::{Config, ConfigBuilder, PasswordPolicyBuilder};
fn main() {
let config = ConfigBuilder::default()
.salt_length(32)
.username_case_mapped(false)
.profanity(true)
.blacklist(false)
.argon2(argon2::Config::default())
.password_policy(
PasswordPolicyBuilder::default()
.min(12)
.max(80)
.build()
.unwrap(),
)
.build()
.unwrap();

View file

@ -5,13 +5,54 @@ use validator_derive::Validate;
use crate::errors::*;
use crate::filters::{beep, filter, forbidden};
/// Credential management configuration
#[derive(Clone, Builder)]
pub struct Config {
/// activates profanity filter. Default `false`
#[builder(default = "false")]
profanity: bool,
/// activates blacklist filter. Default `true`
#[builder(default = "true")]
blacklist: bool,
/// activates username_case_mapped filter. Default `true`
#[builder(default = "true")]
username_case_mapped: bool,
salt_length: usize,
/// activates profanity filter. Default `false`
#[builder(default = "PasswordPolicyBuilder::default().build().unwrap()")]
password_policy: PasswordPolicy,
}
impl PasswordPolicyBuilder {
fn validate(&self) -> Result<(), String> {
if self.min > self.max {
Err("Configuration error: Password max length shorter than min length".to_string())
} else {
Ok(())
}
}
}
#[derive(Clone, Builder)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct PasswordPolicy {
/// See [argon2 config][argon2::Config]
#[builder(default = "argon2::Config::default()")]
argon2: argon2::Config<'static>,
/// minium password length
#[builder(default = "8")]
min: usize,
/// maximum password length(to protect against DoS attacks)
#[builder(default = "64")]
max: usize,
/// salt length in password hashing
#[builder(default = "32")]
salt_length: usize,
}
impl Default for PasswordPolicy {
fn default() -> Self {
PasswordPolicyBuilder::default().build().unwrap()
}
}
#[derive(Validate)]
@ -22,23 +63,12 @@ struct Email {
impl Default for Config {
fn default() -> Self {
Config {
/// profanity filter
profanity: false,
/// blacklist filter
blacklist: true,
/// UsernameCaseMapped filter
username_case_mapped: true,
/// salt length
salt_length: 32,
/// argon2 configuration, see argon2::Processor for more information
argon2: argon2::Config::default(),
}
ConfigBuilder::default().build().unwrap()
}
}
impl Config {
/// process username
/// Mormalises, converts to lowercase and applies filters to the username
pub fn username(&self, username: &str) -> CredsResult<String> {
use ammonia::clean;
use unicode_normalization::UnicodeNormalization;
@ -53,7 +83,7 @@ impl Config {
Ok(clean_username)
}
/// process email
/// Checks if input is an email
pub fn email(&self, email: Option<&str>) -> CredsResult<()> {
if let Some(email) = email {
let email = Email {
@ -77,27 +107,37 @@ impl Config {
Ok(())
}
/// generate hash for password
/// Generate hash for passsword
pub fn password(&self, password: &str) -> CredsResult<String> {
use argon2::hash_encoded;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
let length = password.len();
if self.password_policy.min > length {
return Err(CredsError::PasswordTooShort);
}
if self.password_policy.max < length {
return Err(CredsError::PasswordTooLong);
}
let mut rng = thread_rng();
let salt: String = std::iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(self.salt_length)
.take(self.password_policy.salt_length)
.collect();
Ok(hash_encoded(
password.as_bytes(),
salt.as_bytes(),
&self.argon2,
&self.password_policy.argon2,
)?)
}
/// verify password against hash
/// Verify password against hash
pub fn verify(hash: &str, password: &str) -> CredsResult<bool> {
let status = argon2::verify_encoded(hash, password.as_bytes())?;
Ok(status)
@ -114,33 +154,28 @@ mod tests {
assert!(!config.profanity);
assert!(config.blacklist);
assert!(config.username_case_mapped);
assert_eq!(config.salt_length, 32);
let new_length = 50;
assert_eq!(config.password_policy.salt_length, 32);
let config = ConfigBuilder::default()
.salt_length(new_length)
.username_case_mapped(false)
.profanity(true)
.blacklist(false)
.argon2(argon2::Config::default())
.password_policy(PasswordPolicy::default())
.build()
.unwrap();
assert!(config.profanity);
assert!(!config.blacklist);
assert!(!config.username_case_mapped);
assert_eq!(config.salt_length, new_length);
}
#[test]
fn creds_email_err() {
let config = ConfigBuilder::default()
.salt_length(50)
.username_case_mapped(false)
.profanity(true)
.blacklist(false)
.argon2(argon2::Config::default())
.password_policy(PasswordPolicy::default())
.build()
.unwrap();
@ -167,11 +202,10 @@ mod tests {
#[test]
fn utils_create_new_profane_organisation() {
let config = ConfigBuilder::default()
.salt_length(50)
.username_case_mapped(false)
.profanity(true)
.blacklist(false)
.argon2(argon2::Config::default())
.password_policy(PasswordPolicy::default())
.build()
.unwrap();
@ -187,4 +221,28 @@ mod tests {
assert_eq!(forbidden_err, Err(CredsError::BlacklistError));
}
#[test]
fn password_length_check() {
let min_max_error = PasswordPolicyBuilder::default().min(50).max(10).build();
assert!(min_max_error.is_err());
let config = ConfigBuilder::default()
.password_policy(
PasswordPolicyBuilder::default()
.min(5)
.max(10)
.build()
.unwrap(),
)
.build()
.unwrap();
let too_short_err = config.password("a");
let too_long_err = config.password("asdfasdfasdf");
assert_eq!(too_short_err, Err(CredsError::PasswordTooShort));
assert_eq!(too_long_err, Err(CredsError::PasswordTooLong));
}
}

View file

@ -26,6 +26,14 @@ pub enum CredsError {
#[display(fmt = "The value passed in not an email")]
NotAnEmail,
/// password too short
#[display(fmt = "Password too short")]
PasswordTooShort,
/// password too long
#[display(fmt = "Password too long")]
PasswordTooLong,
/// Errors from argon2
#[display(fmt = "{}", _0)]
Argon2Error(argon2::Error),

View file

@ -33,15 +33,14 @@
//! [ConfigBuilder]:
//!
//!```rust
//! use argon2_creds::{ConfigBuilder, Config};
//! use argon2_creds::{ConfigBuilder, PasswordPolicy, Config};
//!
//! fn main() {
//! let config = ConfigBuilder::default()
//! .salt_length(32)
//! .username_case_mapped(false)
//! .profanity(true)
//! .blacklist(false)
//! .argon2(argon2::Config::default())
//! .password_policy(PasswordPolicy::default())
//! .build()
//! .unwrap();
//!
@ -90,5 +89,5 @@ pub mod config;
pub mod errors;
mod filters;
pub use crate::config::{Config, ConfigBuilder};
pub use crate::config::{Config, ConfigBuilder, PasswordPolicy, PasswordPolicyBuilder};
pub use crate::errors::{CredsError, CredsResult};