#![forbid(unsafe_code)]
#![deny(clippy::mem_forget)]
#![warn(
rust_2018_idioms,
trivial_casts,
unused_qualifications,
overflowing_literals
)]
mod config;
#[macro_use]
mod errors;
use anyhow::Result;
use checkpwn_lib::Password;
use colored::Colorize;
use std::fs::File;
use std::io::{BufReader, Error};
#[cfg(test)]
use assert_cmd::prelude::*;
use std::env;
use std::io::{BufRead, stdin};
use std::panic;
#[cfg(test)]
use std::process::Command;
use zeroize::Zeroize;
fn main() -> Result<()> {
set_checkpwn_panic!(errors::USAGE_ERROR);
assert!(env::args().len() >= 2);
assert!(env::args().len() < 4);
let mut argvs: Vec<String> = env::args().collect();
match argvs[1].to_lowercase().as_str() {
"acc" => {
assert_eq!(argvs.len(), 3);
acc_check(&argvs[2])?;
}
"pass" => {
assert_eq!(argvs.len(), 2);
let hashed_password = Password::new(&rpassword::prompt_password("Password: ")?)?;
let is_breached = checkpwn_lib::check_password(&hashed_password)?;
breach_report(is_breached, "", true);
}
"register" => {
assert_eq!(argvs.len(), 3);
let configuration = config::Config::new();
let config_path = configuration
.get_config_path()
.expect("Failed to determine configuration file path.");
if !config_path.config_file_path.exists() {
match configuration.save_config(&argvs[2]) {
Ok(()) => println!("Successfully saved client configuration."),
Err(e) => panic!("Encountered error saving client configuration: {}", e),
}
} else {
println!(
"A configuration file already exists. Do you want to overwrite it? [y/n]: "
);
let mut overwrite_choice = String::new();
stdin().read_line(&mut overwrite_choice)?;
overwrite_choice = overwrite_choice.to_lowercase();
match overwrite_choice.trim() {
"y" => match configuration.save_config(&argvs[2]) {
Ok(()) => println!("Successfully saved new client configuration."),
Err(e) => panic!("Encountered error saving client configuration: {}", e),
},
"n" => println!("Configuration unchanged. Exiting client."),
_ => panic!("Invalid choice. Please enter 'y' for 'yes' or 'n' for 'no'."),
}
}
}
_ => panic!(),
};
argvs.iter_mut().zeroize();
Ok(())
}
fn breach_report(breached: bool, searchterm: &str, is_password: bool) {
let request_key = if is_password { "********" } else { searchterm };
if breached {
println!(
"Breach status for {}: {}",
request_key.cyan(),
"BREACH FOUND".red()
);
} else {
println!(
"Breach status for {}: {}",
request_key.cyan(),
"NO BREACH FOUND".green()
);
}
}
fn read_file(path: &str) -> Result<BufReader<File>, Error> {
set_checkpwn_panic!(errors::READ_FILE_ERROR);
let file_path = File::open(path).unwrap();
Ok(BufReader::new(file_path))
}
fn strip(string: &str) -> String {
string.replace(['\n', ' ', '\'', '\t'], "")
}
fn acc_breach_request(searchterm: &str, api_key: &str) -> Result<(), checkpwn_lib::CheckpwnError> {
let is_breached = checkpwn_lib::check_account(searchterm, api_key)?;
breach_report(is_breached, searchterm, false);
Ok(())
}
fn acc_check(data_search: &str) -> Result<(), checkpwn_lib::CheckpwnError> {
set_checkpwn_panic!(errors::MISSING_API_KEY);
let mut config = config::Config::new();
config.load_config().unwrap();
if data_search.ends_with(".ls") {
set_checkpwn_panic!(errors::BUFREADER_ERROR);
let file = read_file(data_search).unwrap();
for line_iter in file.lines() {
set_checkpwn_panic!(errors::READLINE_ERROR);
let line = strip(&line_iter.unwrap());
if line.is_empty() {
continue;
}
acc_breach_request(&line, &config.api_key)?;
}
} else {
acc_breach_request(data_search, &config.api_key)?;
}
Ok(())
}
#[test]
fn test_strip_white_new() {
let string_1 = String::from("fkljjsdjlksfdklj dfiwj wefwefwfe");
let string_2 = String::from("derbrererer\n");
let string_3 = String::from("dee\nwfweww rb tte rererer\n");
assert_eq!(&strip(&string_1), "fkljjsdjlksfdkljdfiwjwefwefwfe");
assert_eq!(&strip(&string_2), "derbrererer");
assert_eq!(&strip(&string_3), "deewfwewwrbtterererer");
}
#[test]
fn test_cli_acc_breach() {
let res = Command::new("cargo")
.args(&["run", "acc", "test@example.com"])
.unwrap();
assert!(String::from_utf8_lossy(&res.stdout).contains("BREACH FOUND"));
}
#[test]
fn test_cli_acc_no_breach() {
use rand::RngExt;
use rand::distr::Alphanumeric;
let mut rng = rand::rng();
let email_user: String = (0..8).map(|_| rng.sample(Alphanumeric) as char).collect();
let email_domain: String = (0..8).map(|_| rng.sample(Alphanumeric) as char).collect();
let rnd_email = format!("{:?}@{:?}.com", email_user, email_domain);
let res = Command::new("cargo")
.args(&["run", "acc", &rnd_email])
.unwrap();
assert!(
String::from_utf8_lossy(&res.stdout).contains("NO BREACH FOUND"),
"Found breach for {:?}",
rnd_email
);
}
#[test]
#[should_panic]
fn test_cli_arg_fail() {
Command::new("cargo")
.args(&["run", "wrong", "test@example.com"])
.unwrap()
.assert()
.failure();
}
#[test]
#[should_panic]
fn test_cli_arg_fail_2() {
Command::new("cargo")
.args(&["run"])
.unwrap()
.assert()
.failure();
}
#[test]
#[should_panic]
fn test_cli_arg_fail_3() {
Command::new("cargo")
.args(&["run", "wrong", "test@example.com", "too much"])
.unwrap()
.assert()
.failure();
}
#[test]
fn test_cli_arg_ok() {
Command::new("cargo")
.args(&["run", "acc", "test@example.com"])
.unwrap()
.assert()
.success();
}