sysmonk/squire/
startup.rs

1use std;
2use std::io::Write;
3
4use crate::squire::settings;
5use crate::{constant, squire};
6use chrono::{DateTime, Local};
7use regex::Regex;
8
9/// Initializes the logger based on the provided debug flag and cargo information.
10///
11/// # Arguments
12///
13/// * `debug` - A flag indicating whether to enable debug mode for detailed logging.
14/// * `crate_name` - Name of the crate loaded during compile time.
15pub fn init_logger(debug: bool, utc: bool, crate_name: &String) {
16    if debug {
17        std::env::set_var("RUST_LOG", format!(
18            "actix_web=debug,actix_server=info,{}=debug", crate_name
19        ));
20        std::env::set_var("RUST_BACKTRACE", "1");
21    } else {
22        // Set Actix logging to warning mode since it becomes too noisy when streaming
23        std::env::set_var("RUST_LOG", format!(
24            "actix_web=warn,actix_server=warn,{}=info", crate_name
25        ));
26        std::env::set_var("RUST_BACKTRACE", "0");
27    }
28    let timestamp = if utc {
29        DateTime::<chrono::Utc>::from(Local::now())
30            .format("%Y-%m-%dT%H:%M:%SZ")
31            .to_string()
32    } else {
33        Local::now()
34            .format("%Y-%m-%dT%H:%M:%SZ")
35            .to_string()
36    };
37    env_logger::Builder::from_default_env()
38        .format(move |buf, record| {
39            writeln!(
40                buf,
41                "[{} {} {}:{}] - {}",
42                timestamp,
43                record.level(),
44                record.target(),
45                record.line().unwrap_or(0),
46                record.args()
47            )
48        })
49        .init();
50}
51
52/// Extracts the mandatory env vars by key and parses it as `HashMap<String, String>` and `PathBuf`
53///
54/// # Returns
55///
56/// Returns a tuple of `HashMap<String, String>` and `PathBuf`.
57///
58/// # Panics
59///
60/// If the value is missing or if there is an error parsing the `HashMap`
61fn mandatory_vars() -> (String, String) {
62    let mut errors = "".to_owned();
63    let username = match std::env::var("username") {
64        Ok(val) => val,
65        Err(_) => {
66            errors.push_str(
67                "\nusername\n\texpected a string, received null [value=missing]\n",
68            );
69            "".to_string()
70        }
71    };
72    let password = match std::env::var("password") {
73        Ok(val) => val,
74        Err(_) => {
75            errors.push_str(
76                "\npassword\n\texpected a string, received null [value=missing]\n",
77            );
78            "".to_string()
79        }
80    };
81    if !errors.is_empty() {
82        panic!("{}", errors);
83    }
84    (username, password)
85}
86
87/// Extracts the env var by key and parses it as a `bool`
88///
89/// # Arguments
90///
91/// * `key` - Key for the environment variable.
92///
93/// # Returns
94///
95/// Returns an `Option<bool>` if the value is available.
96///
97/// # Panics
98///
99/// If the value is present, but it is an invalid data-type.
100fn parse_bool(key: &str) -> Option<bool> {
101    match std::env::var(key) {
102        Ok(val) => match val.parse() {
103            Ok(parsed) => Some(parsed),
104            Err(_) => {
105                panic!("\n{}\n\texpected bool, received '{}' [value=invalid]\n", key, val);
106            }
107        },
108        Err(_) => None,
109    }
110}
111
112/// Extracts the env var by key and parses it as a `i64`
113///
114/// # Arguments
115///
116/// * `key` - Key for the environment variable.
117///
118/// # Returns
119///
120/// Returns an `Option<i64>` if the value is available.
121///
122/// # Panics
123///
124/// If the value is present, but it is an invalid data-type.
125fn parse_i64(key: &str) -> Option<i64> {
126    match std::env::var(key) {
127        Ok(val) => match val.parse() {
128            Ok(parsed) => Some(parsed),
129            Err(_) => {
130                panic!("\n{}\n\texpected i64, received '{}' [value=invalid]\n", key, val);
131            }
132        },
133        Err(_) => None,
134    }
135}
136
137/// Extracts the env var by key and parses it as a `u16`
138///
139/// # Arguments
140///
141/// * `key` - Key for the environment variable.
142///
143/// # Returns
144///
145/// Returns an `Option<u16>` if the value is available.
146///
147/// # Panics
148///
149/// If the value is present, but it is an invalid data-type.
150fn parse_u16(key: &str) -> Option<u16> {
151    match std::env::var(key) {
152        Ok(val) => match val.parse() {
153            Ok(parsed) => Some(parsed),
154            Err(_) => {
155                panic!("\n{}\n\texpected u16, received '{}' [value=invalid]\n", key, val);
156            }
157        },
158        Err(_) => None,
159    }
160}
161
162/// Extracts the env var by key and parses it as a `usize`
163///
164/// # Arguments
165///
166/// * `key` - Key for the environment variable.
167///
168/// # Returns
169///
170/// Returns an `Option<usize>` if the value is available.
171///
172/// # Panics
173///
174/// If the value is present, but it is an invalid data-type.
175fn parse_usize(key: &str) -> Option<usize> {
176    match std::env::var(key) {
177        Ok(val) => match val.parse() {
178            Ok(parsed) => Some(parsed),
179            Err(_) => {
180                panic!("\n{}\n\texpected usize, received '{}' [value=invalid]\n", key, val);
181            }
182        },
183        Err(_) => None,
184    }
185}
186
187/// Extracts the env var by key and parses it as a `Vec<String>`
188///
189/// # Arguments
190///
191/// * `key` - Key for the environment variable.
192///
193/// # Returns
194///
195/// Returns an `Option<Vec<String>>` if the value is available.
196///
197/// # Panics
198///
199/// If the value is present, but it is an invalid data-type.
200fn parse_vec(key: &str) -> Option<Vec<String>> {
201    match std::env::var(key) {
202        Ok(val) => match serde_json::from_str::<Vec<String>>(&val) {
203            Ok(parsed) => Some(parsed),
204            Err(_) => {
205                panic!("\n{}\n\texpected vec, received '{}' [value=invalid]\n", key, val);
206            }
207        },
208        Err(_) => None,
209    }
210}
211
212/// Handler that's responsible to parse all the env vars.
213///
214/// # Returns
215///
216/// Instantiates the `Config` struct with the required parameters.
217fn load_env_vars() -> settings::Config {
218    let (username, password) = mandatory_vars();
219    let debug = parse_bool("debug").unwrap_or(settings::default_debug());
220    let utc_logging = parse_bool("utc_logging").unwrap_or(settings::default_utc_logging());
221    let host = std::env::var("host").unwrap_or(settings::default_host());
222    let port = parse_u16("port").unwrap_or(settings::default_port());
223    let session_duration = parse_i64("session_duration").unwrap_or(settings::default_session_duration());
224    let workers = parse_usize("workers").unwrap_or(settings::default_workers());
225    let max_connections = parse_usize("max_connections").unwrap_or(settings::default_max_connections());
226    let websites = parse_vec("websites").unwrap_or(settings::default_websites());
227    settings::Config {
228        username,
229        password,
230        debug,
231        utc_logging,
232        host,
233        port,
234        session_duration,
235        workers,
236        max_connections,
237        websites,
238    }
239}
240
241/// Verifies the strength of a password string.
242///
243/// A secret is considered strong if it meets the following conditions:
244/// - At least 32 characters long
245/// - Contains at least one digit
246/// - Contains at least one uppercase letter
247/// - Contains at least one lowercase letter
248/// - Contains at least one special character
249///
250/// # Arguments
251///
252/// * `password` - A reference to a string slice (`&str`) that represents the password to check.
253///
254/// # Returns
255///
256/// This function returns a `Result<(), String>`.
257/// - `Ok(())` is returned if all conditions are met.
258/// - `Err(String)` is returned with an error message if any condition fails.
259pub fn complexity_checker(password: &str) -> Result<(), String> {
260    let mock_password = "*".repeat(password.len());
261
262    // Check minimum length
263    if password.len() < 8 {
264        return Err(
265            format!(
266                "\npassword\n\t[{}] password must be at least 8 or more characters [value=invalid]\n", mock_password
267            )
268        );
269    }
270
271    // Check for at least one digit
272    let has_digit = Regex::new(r"\d").unwrap();
273    if !has_digit.is_match(password) {
274        return Err(
275            format!(
276                "\npassword\n\t[{}] password must include at least one digit [value=invalid]\n", mock_password
277            )
278        );
279    }
280
281    // Check for at least one uppercase letter
282    let has_uppercase = Regex::new(r"[A-Z]").unwrap();
283    if !has_uppercase.is_match(password) {
284        return Err(
285            format!(
286                "\npassword\n\t[{}] password must include at least one uppercase letter [value=invalid]\n", mock_password
287            )
288        );
289    }
290
291    // Check for at least one lowercase letter
292    let has_lowercase = Regex::new(r"[a-z]").unwrap();
293    if !has_lowercase.is_match(password) {
294        return Err(
295            format!(
296                "\npassword\n\t[{}] password must include at least one lowercase letter [value=invalid]\n", mock_password
297            )
298        );
299    }
300
301    // Check for at least one special character
302    let has_special_char = Regex::new(r###"[ !#$%&'()*+,./:;<=>?@\\^_`{|}~"-]"###).unwrap();
303    if !has_special_char.is_match(password) {
304        return Err(
305            format!(
306                "\npassword\n\t[{}] password must contain at least one special character [value=invalid]\n", mock_password
307            )
308        );
309    }
310    Ok(())
311}
312
313/// Validates all the required environment variables with the required settings.
314///
315/// # Returns
316///
317/// Returns the `Config` struct containing the required parameters.
318fn validate_vars() -> settings::Config {
319    let config = load_env_vars();
320    let mut errors = "".to_owned();
321    if config.username.len() < 4 {
322        let err1 = format!(
323            "\nusername\n\t[{}] username should be at least 4 or more characters [value=invalid]\n",
324            config.username
325        );
326        errors.push_str(&err1);
327    }
328    match complexity_checker(&config.password) {
329        Ok(_) => (),
330        Err(err) => {
331            errors.push_str(&err);
332        }
333    }
334    if !errors.is_empty() {
335        panic!("{}", errors);
336    }
337    config
338}
339
340/// Retrieves the environment variables and parses as the data-type specified in Config struct.
341///
342/// # Arguments
343///
344/// * `metadata` - Struct containing metadata of the application.
345///
346/// # Returns
347///
348/// Converts the config struct into an `Arc` and returns it.
349pub fn get_config(metadata: &constant::MetaData) -> std::sync::Arc<settings::Config> {
350    let mut env_file = squire::parser::arguments(metadata);
351    if env_file.is_empty() {
352        env_file = std::env::var("env_file")
353            .unwrap_or(std::env::var("ENV_FILE")
354                .unwrap_or(".env".to_string()));
355    }
356    let env_file_path = std::env::current_dir()
357        .unwrap_or_default()
358        .join(env_file);
359    let _ = dotenv::from_path(env_file_path.as_path());
360    std::sync::Arc::new(validate_vars())
361}