Skip to content

Commit

Permalink
feat: add encryption functionality and refactor report handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
Kremilly committed Dec 19, 2024
1 parent 7e61c9c commit b1f3b38
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 18 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ documentation = "https://docs.dumpsync.com/"
homepage = "https://dumpsync.com"

[dependencies]
aes-gcm = "0.10.3"
chrono = "0.4.39"
clap = { version = "4.5.21", features = ["cargo", "derive"] }
clap-cargo = "0.14.1"
Expand All @@ -25,9 +26,11 @@ mysql = "25.0.1"
rand = "0.8.5"
regex = "1.11.1"
reqwest = { version = "0.12.9", features = ["blocking"] }
rpassword = "7.3.1"
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.133"
serde_yaml = "0.9.34"
sha2 = "0.10.8"
tokio = { version = "1.41.1", features = ["full"] }

[profile.release]
Expand Down
4 changes: 4 additions & 0 deletions src/args_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub struct ExportOptions {
#[arg(short, long)]
/// Backup path
pub folder: Option<String>,

#[arg(short, long)]
/// Encryption file path
pub encrypt: Option<bool>,
}

#[derive(Parser)]
Expand Down
6 changes: 6 additions & 0 deletions src/core/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct Dump {
dbname: String,
password: String,
dump_file_path: String,
encrypt: Option<bool>,
}

static DUMP_COUNT: AtomicUsize = AtomicUsize::new(0);
Expand All @@ -53,6 +54,7 @@ impl Dump {
backup_path: &str,
interval: Option<u64>,
path: &str,
encrypt: Option<bool>
) -> Self {
Self {
port: port,
Expand All @@ -63,6 +65,7 @@ impl Dump {
dump_file_path: backup_path.to_string(),
interval: interval.unwrap_or(3600),
path: path.to_string(),
encrypt
}
}

Expand All @@ -77,6 +80,7 @@ impl Dump {
password,
&self.dbname,
&dump_file_path,
self.encrypt
).dump().map_err(|_| "Failed to generate dump file")?;

DUMP_COUNT.fetch_add(1, Ordering::SeqCst);
Expand Down Expand Up @@ -108,6 +112,7 @@ impl Dump {
let dbname_clone = self.dbname.clone();
let interval_clone = self.interval;
let path_clone = self.path.clone();
let encrypt_clone = self.encrypt.clone();

ctrlc::set_handler(move || {
running.store(false, Ordering::SeqCst);
Expand All @@ -121,6 +126,7 @@ impl Dump {
interval: interval_clone,
dump_file_path: dump_file_path_clone.clone(),
path: path_clone.clone(),
encrypt: encrypt_clone
};

let dump_count = DUMP_COUNT.load(Ordering::SeqCst);
Expand Down
96 changes: 96 additions & 0 deletions src/core/encrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::{
io::Result,

fs::{
read,
write,
remove_file
},
};

use aes_gcm::{
Key,
Nonce,
Aes256Gcm,

aead::{
Aead,
KeyInit
},
};

use sha2::{
Digest,
Sha256
};

use rpassword::prompt_password;

use crate::{
utils::file::FileUtils,
ui::success_alerts::SuccessAlerts,
};

pub struct Encrypt<'a> {
file_path: &'a str,
}

impl<'a> Encrypt<'a> {

pub fn new(file_path: &'a str) -> Self {
Self { file_path }
}

pub fn encrypt(&self) -> Result<()> {
let user_key = prompt_password("Enter the key (password): ")
.expect("Error reading the password");

let key_hash = Sha256::digest(user_key.as_bytes());
let key = Key::<Aes256Gcm>::from_slice(&key_hash);

let cipher = Aes256Gcm::new(key);

let data = read(&self.file_path)?;

let nonce_bytes = rand::random::<[u8; 12]>();
let nonce = Nonce::from_slice(&nonce_bytes);

let encrypted_data = cipher
.encrypt(nonce, data.as_ref())
.expect("Encryption error");

let encrypted_file_path = format!("{}.aes", &self.file_path);

FileUtils::create_path(&encrypted_file_path);

let mut output = vec![];
output.extend_from_slice(nonce);
output.extend_from_slice(&encrypted_data);
write(&encrypted_file_path, output)?;

remove_file(&self.file_path)?;

SuccessAlerts::dump(&encrypted_file_path);
Ok(())
}

pub fn decrypt_and_read(&self) -> Result<Vec<u8>> {
let user_key = prompt_password("Enter the key (password): ")
.expect("Error reading the password");

let key_hash = Sha256::digest(user_key.as_bytes());
let key = Key::<Aes256Gcm>::from_slice(&key_hash);

let data = read(&self.file_path)?;
let (nonce_bytes, encrypted_data) = data.split_at(12);
let nonce = Nonce::from_slice(nonce_bytes);
let cipher = Aes256Gcm::new(key);

let decrypted_data = cipher
.decrypt(nonce, encrypted_data)
.expect("Decryption error");

Ok(decrypted_data)
}

}
18 changes: 15 additions & 3 deletions src/core/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ use mysql::{

use crate::{
utils::file::FileUtils,
core::connection::Connection,
ui::success_alerts::SuccessAlerts,
handlers::export_handlers::ExportHandlers,

core::{
encrypt::Encrypt,
connection::Connection,
},

helpers::{
configs::Configs,
Expand All @@ -27,6 +31,7 @@ pub struct Export {
pub password: String,
pub dbname: String,
pub dump_file_path: String,
pub encrypt: Option<bool>
}

impl Export {
Expand All @@ -37,7 +42,8 @@ impl Export {
user: &str,
password: &str,
dbname: &str,
dump_file_path: &str
dump_file_path: &str,
encrypt: Option<bool>
) -> Self {
Self {
host: host.to_string(),
Expand All @@ -46,6 +52,7 @@ impl Export {
password: password.to_string(),
dbname: dbname.to_string(),
dump_file_path: dump_file_path.to_string(),
encrypt
}
}

Expand Down Expand Up @@ -93,7 +100,12 @@ impl Export {
writeln!(writer.as_write(), "-- End of table `{}`", table)?;
}

SuccessAlerts::dump(&dump_file_path);
if self.encrypt.unwrap_or(false) {
let _ = Encrypt::new(&dump_file_path).encrypt();
} else {
SuccessAlerts::dump(&dump_file_path);
}

Ok(())
}

Expand Down
68 changes: 65 additions & 3 deletions src/core/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ use std::{

use crate::{
constants::regexp::RegExp,
core::connection::Connection,
handlers::import_handlers::ImportHandlers,

core::{
encrypt::Encrypt,
connection::Connection,
},

ui::{
errors_alerts::ErrorsAlerts,
success_alerts::SuccessAlerts,
Expand All @@ -44,7 +48,15 @@ pub struct Import {

impl Import {

pub fn new(host: &str, port: u16, user: &str, password: &str, dbname: &str, dump_file_path: &str, path: &str) -> Self {
pub fn new(
host: &str,
port: u16,
user: &str,
password: &str,
dbname: &str,
dump_file_path: &str,
path: &str,
) -> Self {
Self {
host: host.to_string(),
port,
Expand All @@ -67,7 +79,46 @@ impl Import {
}
}

pub fn dump(&self) -> Result<(), Box<dyn Error>> {
pub fn dump_encrypted(&self) -> Result<(), Box<dyn Error>> {
let pool = Connection {
host: self.host.clone(),
port: self.port,
user: self.user.clone(),
password: self.password.clone(),
dbname: Some(self.dbname.clone()),
}.create_pool()?;

let mut conn = pool.get_conn()?;

let decrypt = Encrypt::new(&self.dump_file_path);
let dump_content = String::from_utf8(decrypt.decrypt_and_read()?)?;

let dump_content = ImportHandlers::new(&self.dbname, &dump_content).check_db_name();

let create_table_regex = Regex::new(RegExp::CREATE_TABLE).unwrap();

for statement in dump_content.split(';') {
let trimmed = statement.trim();

if !trimmed.is_empty() {
match conn.query_drop(trimmed) {
Ok(_) => {
if let Some(captures) = create_table_regex.captures(trimmed) {
if let Some(table_name) = captures.get(1) {
SuccessAlerts::table(table_name.as_str());
}
}
}
Err(e) => ErrorsAlerts::import(&self.dbname, trimmed, &e.to_string()),
}
}
}

SuccessAlerts::import(&self.dbname);
Ok(())
}

pub fn dump_plain(&self) -> Result<(), Box<dyn Error>> {
let pool = Connection {
host: self.host.clone(),
port: self.port,
Expand Down Expand Up @@ -113,6 +164,7 @@ impl Import {
}
}
}

Err(e) => ErrorsAlerts::import(&self.dbname, trimmed, &e.to_string()),
}
}
Expand All @@ -122,4 +174,14 @@ impl Import {
Ok(())
}

pub fn dump(&self) -> Result<(), Box<dyn Error>> {
if self.dump_file_path.ends_with(".aes") {
let _ = self.dump_encrypted();
} else {
let _ = self.dump_plain();
}

Ok(())
}

}
1 change: 1 addition & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod dump;
pub mod export;
pub mod import;
pub mod encrypt;
pub mod transfer;
pub mod connection;
4 changes: 4 additions & 0 deletions src/dump_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl DumpSync {
&backup_path,
None,
&backup_path,
None
).import();
}

Expand All @@ -89,6 +90,7 @@ impl DumpSync {
Env::get_var_u64("DS_DUMP_INTERVAL")
});

let encrypt = options.encrypt;
let backup_path = options.folder.unwrap_or_else(|| Env::get_var("DS_DUMP_PATH"));
let (dbname, host, user, password, port) = self.load_db_config();

Expand All @@ -104,6 +106,7 @@ impl DumpSync {
&backup_path,
Some(interval),
&backup_path,
encrypt
).export();
}

Expand Down Expand Up @@ -179,6 +182,7 @@ impl DumpSync {
&backup_path,
None,
&backup_path,
None
).transfer();
}

Expand Down
Loading

0 comments on commit b1f3b38

Please sign in to comment.