diff options
| author | salmonik <raisersoft@proton.me> | 2026-02-28 19:32:20 +0100 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-02-28 19:32:20 +0100 |
| commit | 17082597464198fe84be80313b7d51e1e57de639 (patch) | |
| tree | 7b2c7b90c38e3c47e1839247174df2161b736df4 /src/client/database.rs | |
| parent | remove Cargo.lock from git tracking (diff) | |
| download | zagan-bet-17082597464198fe84be80313b7d51e1e57de639.tar.gz zagan-bet-17082597464198fe84be80313b7d51e1e57de639.zip | |
folder restructuring
Diffstat (limited to 'src/client/database.rs')
| -rw-r--r-- | src/client/database.rs | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/client/database.rs b/src/client/database.rs new file mode 100644 index 0000000..03a6c45 --- /dev/null +++ b/src/client/database.rs | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | use rusqlite::Connection; | ||
| 2 | use aes_gcm::{Aes256Gcm, Key, Nonce}; | ||
| 3 | use aes_gcm::aead::{Aead, KeyInit}; | ||
| 4 | use pbkdf2::pbkdf2; | ||
| 5 | use hmac::Hmac; | ||
| 6 | use sha2::Sha256; | ||
| 7 | use rand::Rng; | ||
| 8 | |||
| 9 | pub fn init_db() -> Connection { | ||
| 10 | let conn = Connection::open("client.db").unwrap(); | ||
| 11 | conn.execute( | ||
| 12 | "CREATE TABLE IF NOT EXISTS hosts ( | ||
| 13 | host TEXT PRIMARY KEY, | ||
| 14 | last_used DATETIME DEFAULT CURRENT_TIMESTAMP | ||
| 15 | )", | ||
| 16 | [], | ||
| 17 | ).unwrap(); | ||
| 18 | conn.execute( | ||
| 19 | "CREATE TABLE IF NOT EXISTS credentials ( | ||
| 20 | host TEXT PRIMARY KEY, | ||
| 21 | username TEXT NOT NULL, | ||
| 22 | encrypted_password BLOB NOT NULL | ||
| 23 | )", | ||
| 24 | [], | ||
| 25 | ).unwrap(); | ||
| 26 | conn | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn get_last_host(conn: &Connection) -> Option<String> { | ||
| 30 | let mut stmt = conn.prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1").unwrap(); | ||
| 31 | let mut rows = stmt.query([]).unwrap(); | ||
| 32 | if let Ok(Some(row)) = rows.next() { | ||
| 33 | row.get(0).ok() | ||
| 34 | } else { | ||
| 35 | None | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn save_host(conn: &Connection, host: &str) { | ||
| 40 | conn.execute( | ||
| 41 | "INSERT OR REPLACE INTO hosts (host, last_used) VALUES (?1, CURRENT_TIMESTAMP)", | ||
| 42 | [host], | ||
| 43 | ).unwrap(); | ||
| 44 | } | ||
| 45 | |||
| 46 | pub fn save_credentials(conn: &Connection, host: &str, username: &str, password: &str) -> Result<(), aes_gcm::Error> { | ||
| 47 | let encrypted = encrypt_password(password)?; | ||
| 48 | conn.execute( | ||
| 49 | "INSERT OR REPLACE INTO credentials (host, username, encrypted_password) VALUES (?1, ?2, ?3)", | ||
| 50 | (host, username, &encrypted), | ||
| 51 | ).unwrap(); | ||
| 52 | Ok(()) | ||
| 53 | } | ||
| 54 | |||
| 55 | pub fn get_credentials(conn: &Connection, host: &str) -> Option<(String, String)> { | ||
| 56 | let mut stmt = conn.prepare("SELECT username, encrypted_password FROM credentials WHERE host = ?1").unwrap(); | ||
| 57 | let mut rows = stmt.query([host]).unwrap(); | ||
| 58 | if let Ok(Some(row)) = rows.next() { | ||
| 59 | let username: String = row.get(0).unwrap(); | ||
| 60 | let encrypted: Vec<u8> = row.get(1).unwrap(); | ||
| 61 | decrypt_password(&encrypted).map(|password| (username, password)) | ||
| 62 | } else { | ||
| 63 | None | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | fn derive_key() -> Key<Aes256Gcm> { | ||
| 68 | let mut key = [0u8; 32]; | ||
| 69 | let salt = b"client_salt"; // Fixed salt for simplicity | ||
| 70 | pbkdf2::<Hmac<Sha256>>(b"client_key", salt, 10000, &mut key).expect("error deriving key"); | ||
| 71 | key.into() | ||
| 72 | } | ||
| 73 | |||
| 74 | fn encrypt_password(password: &str) -> Result<Vec<u8>, aes_gcm::Error> { | ||
| 75 | let key = derive_key(); | ||
| 76 | let cipher = Aes256Gcm::new(&key); | ||
| 77 | let mut nonce_bytes = [0u8; 12]; | ||
| 78 | rand::thread_rng().fill(&mut nonce_bytes); | ||
| 79 | let nonce = Nonce::from_slice(&nonce_bytes); | ||
| 80 | let ciphertext = cipher.encrypt(nonce, password.as_bytes())?; | ||
| 81 | let mut result = nonce_bytes.to_vec(); | ||
| 82 | result.extend(ciphertext); | ||
| 83 | Ok(result) | ||
| 84 | } | ||
| 85 | |||
| 86 | fn decrypt_password(encrypted: &[u8]) -> Option<String> { | ||
| 87 | if encrypted.len() < 12 { | ||
| 88 | return None; | ||
| 89 | } | ||
| 90 | let key = derive_key(); | ||
| 91 | let cipher = Aes256Gcm::new(&key); | ||
| 92 | let nonce = Nonce::from_slice(&encrypted[..12]); | ||
| 93 | cipher.decrypt(nonce, &encrypted[12..]).ok().and_then(|plaintext| String::from_utf8(plaintext).ok()) | ||
| 94 | } | ||
