pub mod gen_engine;
pub mod lang;
use crate::gen_engine::gen_engine::{LETTERS_CHARSET, NUM_CHARSET, SPEC_SYMB_CHARSET, U_LETTERS_CHARSET};
use crate::lang::lang::{Language, StrengthTranslations};
pub struct Passgen {
pub enab_letters: bool,
pub enab_u_letters: bool,
pub enab_num: bool,
pub enab_spec_symbs: bool,
pub enab_strong_usab: bool,
pub custom_charset: &'static str,
pub password: String,
pub language: Language,
}
impl Passgen {
pub fn new() -> Passgen {
Passgen {
enab_letters: false,
enab_u_letters: false,
enab_num: false,
enab_spec_symbs: false,
enab_strong_usab: false,
custom_charset: "",
password: String::new(),
language: Language::English,
}
}
pub fn default() -> Passgen {
Passgen {
enab_letters: true,
enab_u_letters: true,
enab_num: true,
enab_spec_symbs: true,
enab_strong_usab: false,
custom_charset: "",
password: String::new(),
language: Language::English,
}
}
pub fn default_strong_and_usab() -> Passgen {
Passgen {
enab_letters: false,
enab_u_letters: false,
enab_num: false,
enab_spec_symbs: false,
custom_charset: "",
enab_strong_usab: true,
password: String::new(),
language: Language::English,
}
}
pub fn set_enabled_letters(&mut self, value: bool) -> &mut Passgen {
self.enab_letters = value;
self
}
pub fn set_enabled_uppercase_letters(&mut self, value: bool) -> &mut Passgen {
self.enab_u_letters = value;
self
}
pub fn set_enabled_numbers(&mut self, value: bool) -> &mut Passgen {
self.enab_num = value;
self
}
pub fn set_enabled_spec_symbols(&mut self, value: bool) -> &mut Passgen {
self.enab_spec_symbs = value;
self
}
pub fn set_enabled_strong_usab(&mut self, value: bool) -> &mut Passgen {
self.enab_strong_usab = value;
self
}
pub fn set_custom_charset(&mut self, value: &'static str) -> &mut Passgen {
self.custom_charset = value;
self
}
pub fn set_password(&mut self, password: &str) -> &mut Passgen {
self.password = password.to_string();
self
}
pub fn get_password(&self) -> &str {
&self.password
}
pub fn set_language(&mut self, language: Language) -> &mut Passgen {
self.language = language;
self
}
pub fn generate(&mut self, length: u32) -> String {
if !self.is_ruleset_clean() {
let res_len = if length < 4 { 4 } else { length };
let mut pwd = self.generate_pass(res_len);
if self.custom_charset.len() == 0 {
while !self.validate_password_rules(pwd.clone()) {
pwd = self.generate_pass(res_len);
}
}
self.password = pwd.clone();
pwd
} else {
self.password.clear();
"".to_string()
}
}
pub fn validate_password(&self) -> bool {
if self.password.is_empty() {
return false;
}
if self.custom_charset.len() > 0 {
for ch in self.password.chars() {
if !self.custom_charset.contains(ch) {
return false;
}
}
return true;
}
self.validate_password_rules(self.password.clone())
}
pub fn password_strength_score(&self) -> u8 {
if self.password.is_empty() {
return 0;
}
let password = &self.password;
let length = password.len();
if length < 4 {
return 0;
}
let mut score = 0i32;
score += match length {
0..=4 => 0,
5..=6 => 5,
7..=8 => 10,
9..=10 => 15,
11..=12 => 20,
_ => 25,
};
let mut has_lowercase = false;
let mut has_uppercase = false;
let mut has_digits = false;
let mut has_special = false;
let mut unique_chars = std::collections::HashSet::new();
for ch in password.chars() {
unique_chars.insert(ch);
if ch.is_ascii_lowercase() {
has_lowercase = true;
} else if ch.is_ascii_uppercase() {
has_uppercase = true;
} else if ch.is_ascii_digit() {
has_digits = true;
} else if ch.is_ascii_punctuation() || "[]{}()<>".contains(ch) {
has_special = true;
}
}
let char_type_count = [has_lowercase, has_uppercase, has_digits, has_special]
.iter()
.filter(|&&x| x)
.count();
let mut variety_score = 0;
if has_lowercase { variety_score += 5; }
if has_uppercase { variety_score += 5; }
if has_digits { variety_score += 5; }
if has_special { variety_score += 10; }
match char_type_count {
2 => variety_score += 5,
3 => variety_score += 10,
4 => variety_score += 15,
_ => {}
}
score += variety_score.min(25);
let uniqueness_ratio = unique_chars.len() as f32 / length as f32;
score += (uniqueness_ratio * 20.0) as i32;
let weak_passwords = [
"password", "123456", "qwerty", "admin", "welcome",
"12345678", "123456789", "12345", "1234", "111111",
];
let lower_pwd = password.to_lowercase();
for weak in &weak_passwords {
if lower_pwd == *weak {
return 0;
}
}
let weak_patterns = ["password", "123", "qwerty", "admin", "letmein"];
let mut pattern_penalty = 0;
for pattern in &weak_patterns {
if lower_pwd.contains(pattern) {
pattern_penalty += 15;
}
}
let mut penalty = pattern_penalty;
let chars: Vec<char> = password.chars().collect();
for i in 0..chars.len().saturating_sub(2) {
let c1 = chars[i] as u32;
let c2 = chars[i + 1] as u32;
let c3 = chars[i + 2] as u32;
if c2 == c1 + 1 && c3 == c2 + 1 {
penalty += 10;
break;
}
}
for i in 0..chars.len().saturating_sub(2) {
if chars[i] == chars[i + 1] && chars[i] == chars[i + 2] {
penalty += 10;
break;
}
}
if char_type_count < 2 {
penalty += 10;
}
score -= penalty.min(30);
let mut charset_size = 0;
if has_lowercase { charset_size += 26; }
if has_uppercase { charset_size += 26; }
if has_digits { charset_size += 10; }
if has_special { charset_size += 32; }
if charset_size > 0 {
let entropy_per_char = (charset_size as f32).log2();
let total_entropy = length as f32 * entropy_per_char;
let entropy_score = (total_entropy / 10.0).min(10.0);
score += entropy_score as i32;
}
score = score.max(0).min(100);
score as u8
}
pub fn password_strength_level(&self) -> &'static str {
let score = self.password_strength_score();
StrengthTranslations::get_level(self.language, score)
}
fn is_ruleset_clean(&self) -> bool {
!self.enab_letters
&& !self.enab_u_letters
&& !self.enab_num
&& !self.enab_spec_symbs
&& !self.enab_strong_usab
&& self.custom_charset.len() == 0
}
fn validate_password_rules(&self, pass: String) -> bool {
let check_to_available_for = |symbols: &str| -> bool {
let mut res = false;
for ch in pass.chars() {
if symbols.contains(ch) {
res = true;
break;
}
}
res
};
if self.enab_letters || self.enab_strong_usab {
if !check_to_available_for(LETTERS_CHARSET) {
return false;
}
}
if self.enab_u_letters || self.enab_strong_usab {
if !check_to_available_for(U_LETTERS_CHARSET) {
return false;
}
}
if self.enab_num || self.enab_strong_usab {
if !check_to_available_for(NUM_CHARSET) {
return false;
}
}
if self.enab_spec_symbs || self.enab_strong_usab {
if !check_to_available_for(SPEC_SYMB_CHARSET) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests;