mk_pass/config.rs
1use crate::helpers::{DECIMAL, LOWERCASE, SPECIAL_CHARACTERS, UPPERCASE};
2
3#[cfg(feature = "clap")]
4use clap::{ArgAction, Parser};
5
6/// A structure to describe password requirements.
7#[cfg_attr(
8 feature = "clap",
9 derive(Parser),
10 command(about = "Generate a password comprehensively.", version, long_about = None)
11)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct PasswordRequirements {
14 /// The length of the password.
15 #[cfg_attr(feature = "clap", arg(long, short, default_value = "16"))]
16 pub length: u16,
17
18 /// How many decimal integer characters should the password contain?
19 #[cfg_attr(feature = "clap", arg(long, short, default_value = "1"))]
20 pub decimal: u16,
21
22 /// How many special characters should the password contain?
23 #[cfg_attr(feature = "clap", arg(long, short, default_value = "1"))]
24 pub specials: u16,
25
26 /// Should the first character always be a letter?
27 #[cfg_attr(
28 feature = "clap",
29 arg(
30 long = "no-first-is-letter",
31 short,
32 help = "Do not restrict the first character to only letters.",
33 long_help = "Do not restrict the first character to only letters.\
34 \n\nBy default, the first character is always a letter.",
35 action = ArgAction::SetFalse
36 )
37 )]
38 pub first_is_letter: bool,
39
40 /// Allow characters to be used more than once?
41 #[cfg_attr(
42 feature = "clap",
43 arg(
44 short = 'r',
45 long,
46 help = "Allow character to used more than once.",
47 long_help = "Allow character to used more than once.\
48 \n\nBy default, each generated character is only used once.\n\
49 Allowing repetitions also relaxes the maximum length.",
50 action = ArgAction::SetTrue
51 )
52 )]
53 pub allow_repeats: bool,
54}
55
56impl PasswordRequirements {
57 /// Validates the instance's values.
58 ///
59 /// This returns a mutated copy of the instance where the values satisfy
60 /// "sane minimum requirements" suitable for any password.
61 ///
62 /// The phrase "sane minimum requirements" implies
63 ///
64 /// 1. `length` is not less than 10
65 /// 2. To avoid repetitions, `length` is not more than
66 ///
67 /// - 52 if only letters (no decimal integers or special characters) are used
68 /// - 62 if only letters and decimal integers are used
69 /// - 68 if only letters and special characters are used
70 /// - 78 if letters, decimal integers, and special characters are used
71 /// - [u16::MAX] if repeated characters are allowed
72 /// 3. `specials` character count does not overrule the required number of
73 ///
74 /// - letters (2; 1 uppercase and 1 lowercase)
75 /// - decimal integers (if `decimal` is specified as non-zero value)
76 /// 4. `decimal` character count does not overrule the required number of
77 ///
78 /// - letters (2; 1 uppercase and 1 lowercase)
79 /// - special characters (if `specials` is specified as non-zero value)
80 ///
81 /// # About resolving conflicts
82 ///
83 /// If this function finds a conflict between the specified number of
84 /// `specials` characters and `decimal`, then decimal integers takes precedence.
85 ///
86 /// For example:
87 ///
88 /// ```rust
89 /// use mk_pass::PasswordRequirements;
90 /// let req = PasswordRequirements {
91 /// length: 16,
92 /// specials: 16,
93 /// decimal: 16,
94 /// ..Default::default()
95 /// };
96 /// let expected = PasswordRequirements {
97 /// length: 16,
98 /// specials: 1,
99 /// decimal: 13,
100 /// ..Default::default()
101 /// };
102 /// assert_eq!(req.validate(), expected);
103 /// ```
104 pub fn validate(&self) -> Self {
105 let mut len = self.length.max(10);
106 if !self.allow_repeats {
107 len = len.min(
108 UPPERCASE.len() as u16
109 + LOWERCASE.len() as u16
110 + {
111 if self.specials > 0 {
112 SPECIAL_CHARACTERS.len() as u16
113 } else {
114 0
115 }
116 }
117 + {
118 if self.decimal > 0 {
119 DECIMAL.len() as u16
120 } else {
121 0
122 }
123 },
124 );
125 }
126 let non_letter_max_len = len - 2;
127 let max_special = if self.specials > 0 {
128 non_letter_max_len - self.decimal.min(non_letter_max_len - 1)
129 } else {
130 0
131 };
132 let max_decimal = non_letter_max_len - max_special;
133 Self {
134 length: len,
135 decimal: self.decimal.min(max_decimal),
136 specials: self.specials.min(max_special),
137 first_is_letter: self.first_is_letter,
138 allow_repeats: self.allow_repeats,
139 }
140 }
141}
142
143impl Default for PasswordRequirements {
144 /// Create default password requirements.
145 fn default() -> Self {
146 Self {
147 length: 16,
148 decimal: 1,
149 specials: 1,
150 first_is_letter: true,
151 allow_repeats: false,
152 }
153 }
154}