From 030b5ccb54955285d4c9b330fdcc413a1e62de08 Mon Sep 17 00:00:00 2001 From: Danial Raza Date: Sun, 28 Jan 2024 00:28:37 +0100 Subject: [PATCH] feat: introduce password history (#1) --- Cargo.lock | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- src/main.rs | 74 ++++++++++++++++++++++++++++++--- 3 files changed, 185 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c2a1e5..19f7afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,10 @@ name = "accesskit" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea" +dependencies = [ + "enumn", + "serde", +] [[package]] name = "accesskit_consumer" @@ -104,6 +108,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -416,6 +421,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "bitflags" version = "1.3.2" @@ -427,6 +438,9 @@ name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -789,6 +803,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -817,6 +852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57539aabcdbb733b6806ef421b66dec158dc1582107ad6d51913db3600303354" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -827,6 +863,7 @@ checksum = "79c00143a1d564cf27570234c9a199cbe75dc3d43a135510fb2b93406a87ee8e" dependencies = [ "bytemuck", "cocoa", + "directories-next", "egui", "egui-winit", "egui_glow", @@ -840,6 +877,8 @@ dependencies = [ "parking_lot", "percent-encoding", "raw-window-handle 0.5.2", + "ron", + "serde", "static_assertions", "thiserror", "wasm-bindgen", @@ -860,6 +899,8 @@ dependencies = [ "epaint", "log", "nohash-hasher", + "ron", + "serde", ] [[package]] @@ -873,6 +914,7 @@ dependencies = [ "egui", "log", "raw-window-handle 0.5.2", + "serde", "smithay-clipboard", "web-time", "webbrowser", @@ -901,6 +943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee58355767587db7ba3738930d93cad3052cd834c2b48b9ef6ef26fe4823b7e" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -924,6 +967,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "enumn" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "epaint" version = "0.25.0" @@ -938,6 +992,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", + "serde", ] [[package]] @@ -1362,6 +1417,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "jni" version = "0.21.1" @@ -1430,6 +1491,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "libredox" version = "0.0.2" @@ -1689,7 +1761,7 @@ version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox", + "libredox 0.0.2", ] [[package]] @@ -1747,6 +1819,7 @@ dependencies = [ "copypasta", "eframe", "rand", + "serde_json", ] [[package]] @@ -1945,6 +2018,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox 0.0.1", + "thiserror", +] + [[package]] name = "regex" version = "1.10.3" @@ -1974,6 +2058,18 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.2", + "serde", + "serde_derive", +] + [[package]] name = "rustix" version = "0.37.27" @@ -2001,6 +2097,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "same-file" version = "1.0.6" @@ -2055,6 +2157,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.18" diff --git a/Cargo.toml b/Cargo.toml index afa3aa0..02c6f63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] copypasta = "0.10.0" -eframe = "0.25.0" +eframe = { version = "0.25.0", features = ["persistence"] } rand = "0.8.5" +serde_json = "1.0.111" diff --git a/src/main.rs b/src/main.rs index 83d65c0..a3fdabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,30 +6,50 @@ mod random_password; use constants::MAX_PASSWORD_LENGTH; use random_password::{RandomPassword, RandomPasswordOptions}; +use std::time::Duration; + use copypasta::{ClipboardContext, ClipboardProvider}; use eframe::{ egui::{CentralPanel, Context, RichText, Slider, ViewportBuilder}, - App, Error, Frame, NativeOptions, + run_native, App, Error, Frame, NativeOptions, }; +use serde_json::{from_str, json}; fn main() -> Result<(), Error> { let options = NativeOptions { centered: true, follow_system_theme: true, - viewport: ViewportBuilder::default().with_inner_size([300.0, 200.0]), + viewport: ViewportBuilder::default() + .with_app_id("password-generator") + .with_inner_size([300.0, 200.0]), ..Default::default() }; - eframe::run_native( + run_native( "Password Generator", options, - Box::new(|_app_creator| Box::::default()), + Box::new(|creation_context| { + let storage = match creation_context.storage { + Some(storage) => storage, + None => return Box::::default(), + }; + + let recent_passwords = match storage.get_string("recent_passwords") { + Some(passwords) => from_str(passwords.as_str()).unwrap_or_default(), + None => return Box::::default(), + }; + + Box::new(PasswordGenerator { + recent_passwords, + ..Default::default() + }) + }), ) } - struct PasswordGenerator { options: RandomPasswordOptions, password: String, + recent_passwords: Vec, } impl Default for PasswordGenerator { @@ -37,11 +57,35 @@ impl Default for PasswordGenerator { Self { options: RandomPasswordOptions::default(), password: RandomPassword::new(RandomPasswordOptions::default()).password, + recent_passwords: Vec::with_capacity(10), } } } impl App for PasswordGenerator { + fn save(&mut self, storage: &mut dyn eframe::Storage) { + let mut passwords_to_add: Vec = Vec::with_capacity(10); + + passwords_to_add.push(self.password.clone()); + + let mut recent_passwords = match storage.get_string("recent_passwords") { + Some(passwords) => { + let passwords: Vec<_> = from_str(passwords.as_str()).unwrap_or_default(); + + passwords_to_add.extend(passwords); + + passwords_to_add + } + None => passwords_to_add, + }; + + recent_passwords.sort(); + + recent_passwords.dedup(); + + storage.set_string("recent_passwords", json!(recent_passwords).to_string()); + } + fn update(&mut self, ctx: &Context, _frame: &mut Frame) { CentralPanel::default().show(ctx, |ui| { ui.label(RichText::new(self.password.to_string()).size(18.0)); @@ -69,6 +113,12 @@ impl App for PasswordGenerator { } self.password = RandomPassword::new(self.options).password; + + if self.recent_passwords.len() >= 10 { + self.recent_passwords.pop(); + } + + self.recent_passwords.push(self.password.clone()); } if ui.button(RichText::new("Copy")).clicked() { @@ -76,6 +126,20 @@ impl App for PasswordGenerator { let _copied = context.set_contents(self.password.to_string()); } + + ui.separator(); + + ui.heading("Recent Passwords"); + + ui.vertical(|ui| { + for password in self.recent_passwords.iter().rev() { + ui.label(RichText::new(password.to_string()).size(14.0)); + } + }); }); } + + fn auto_save_interval(&self) -> std::time::Duration { + Duration::from_secs(5) + } }