diff --git a/src/main.rs b/src/main.rs index 2418075..0f4208d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use egui::{Color32, Context, RichText, TextEdit, Widget, WidgetText}; use log::info; use native_dialog::MessageDialog; use simple_logger::SimpleLogger; -use crate::notes::{Note, NoteMetadata}; +use crate::notes::{DecryptedNote, Note, NoteMetadata}; use crate::password::{check_password, entropy, generate_new_password, migrate_to_password_v1, PasswordSystem}; use crate::saving::Saving; @@ -108,32 +108,37 @@ impl eframe::App for App { if !self.tested_password && self.password.is_some() { if let Some(note) = self.notes.get(0) { - if let Ok(password) = check_password(self.password.as_ref().unwrap(), note) { - match password { - PasswordSystem::V0(password) => { - info!("migrating from PasswordSystem::V0 to PasswordSystem::V1"); - info!("backing up notes"); - self.saving.backup_notes().expect("failed to back up notes"); - info!("backed up notes successfully!"); - info!("starting migration"); - self.password = Some( - migrate_to_password_v1(&password, &self.notes, &self.saving) - .expect("failed to migrate notes") - ); - self.notes = self.saving.read_notes().expect("failed to update notes after migration"); - info!("migration was successful!"); - }, - PasswordSystem::V1(password) => self.password = Some(password), - } - } else { - let _ = MessageDialog::new() - .set_title("invalid password") - .set_text("failed to verify the password against an existing note. please try again") - .show_alert(); + match note { + Note::Encrypted(note) => { + if let Ok(password) = check_password(self.password.as_ref().unwrap(), note) { + match password { + PasswordSystem::V0(password) => { + info!("migrating from PasswordSystem::V0 to PasswordSystem::V1"); + info!("backing up notes"); + self.saving.backup_notes().expect("failed to back up notes"); + info!("backed up notes successfully!"); + info!("starting migration"); + self.password = Some( + migrate_to_password_v1(&password, &self.notes, &self.saving) + .expect("failed to migrate notes") + ); + self.notes = self.saving.read_notes().expect("failed to update notes after migration"); + info!("migration was successful!"); + }, + PasswordSystem::V1(password) => self.password = Some(password), + } + } else { + let _ = MessageDialog::new() + .set_title("invalid password") + .set_text("failed to verify the password against an existing note. please try again") + .show_alert(); - self.password = None; - self.tested_password = false; - return; + self.password = None; + self.tested_password = false; + return; + } + } + _ => unreachable!() } } else { self.password = Some(generate_new_password(self.password.as_ref().unwrap())) @@ -245,7 +250,7 @@ impl eframe::App for App { .then_some(metadata) }); - let note = Note::Decrypted { + let note = Note::Decrypted(DecryptedNote { id: SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() @@ -257,9 +262,9 @@ impl eframe::App for App { arbitrary: metadata, }, text: self.text_buffer.clone(), - }; + }); - self.saving.save_note(note.clone(), password).unwrap(); + self.saving.save_note(¬e.clone(), password).unwrap(); let mut vec_deque = VecDeque::from(self.notes.clone()); vec_deque.push_front(note); @@ -353,7 +358,10 @@ impl eframe::App for App { if let Some(save_path) = save_path { let notes: Vec = self.notes.clone() .iter() - .map(|note| note.decrypt(password).expect("failed to decrypt note")) + .map(|note| match note { + Note::Encrypted(note) => note.decrypt(password).expect("failed to decrypt note").into(), + _ => note.clone(), + }) .collect(); let json_notes = serde_json::to_string(¬es).unwrap(); diff --git a/src/notes/mod.rs b/src/notes/mod.rs index 201bf3c..6172bc5 100644 --- a/src/notes/mod.rs +++ b/src/notes/mod.rs @@ -12,125 +12,133 @@ pub struct NoteMetadata { pub arbitrary: Option>, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EncryptedNote { + pub id: String, + pub title: String, + pub metadata: NoteMetadata, + pub encrypted_text: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DecryptedNote { + pub id: String, + pub title: String, + pub metadata: NoteMetadata, + pub text: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HiddenNote(pub DecryptedNote); + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Note { - Encrypted { - id: String, - title: String, - metadata: NoteMetadata, - encrypted_text: String, - }, - Decrypted { - id: String, - title: String, - metadata: NoteMetadata, - text: String, - }, - Hidden { - id: String, - title: String, - metadata: NoteMetadata, - text: String, + Encrypted(EncryptedNote), + Decrypted(DecryptedNote), + Hidden(HiddenNote), +} + +impl DecryptedNote { + pub fn encrypt(&self, password: &str) -> anyhow::Result { + let mut rng = rand::thread_rng(); + + let pwbox = Sodium::build_box(&mut rng) + .seal(password.to_string().into_bytes(), self.text.clone().into_bytes())?; + + let mut eraser = Eraser::new(); + eraser.add_suite::(); + let erased: ErasedPwBox = eraser.erase(&pwbox)?; + + Ok(EncryptedNote { + id: self.id.clone(), + title: self.title.clone(), + metadata: self.metadata.clone(), + encrypted_text: serde_json::to_string(&erased)?, + }) + } + + pub fn hide(self) -> HiddenNote { + HiddenNote(self) + } +} + +impl EncryptedNote { + pub fn decrypt(&self, password: &str) -> anyhow::Result { + let mut eraser = Eraser::new(); + eraser.add_suite::(); + let erased: ErasedPwBox = serde_json::from_str(&self.encrypted_text)?; + + let decrypted_bytes = eraser.restore(&erased)?.open(password)?.to_vec(); + let decrypted_text = String::from_utf8(decrypted_bytes)?; + + Ok(DecryptedNote { + id: self.id.clone(), + title: self.title.clone(), + metadata: self.metadata.clone(), + text: decrypted_text, + }) + } +} + +impl HiddenNote { + pub fn unhide(self) -> DecryptedNote { + self.0 + } +} + +impl From for Note { + fn from(value: HiddenNote) -> Self { + Self::Hidden(value) + } +} + +impl From for Note { + fn from(value: DecryptedNote) -> Self { + Self::Decrypted(value) + } +} + +impl From for Note { + fn from(value: EncryptedNote) -> Self { + Self::Encrypted(value) } } impl Note { - pub fn encrypt(&self, password: &str) -> anyhow::Result { - let mut rng = rand::thread_rng(); - - match self { - Note::Decrypted { id, text, metadata, title } => { - let pwbox = Sodium::build_box(&mut rng) - .seal(password.to_string().into_bytes(), text.clone().into_bytes())?; - - let mut eraser = Eraser::new(); - eraser.add_suite::(); - let erased: ErasedPwBox = eraser.erase(&pwbox)?; - - Ok(Self::Encrypted { - id: id.clone(), - title: title.clone(), - metadata: metadata.clone(), - encrypted_text: serde_json::to_string(&erased)?, - }) - } - _ => Ok(self.clone()), - } - } - - pub fn decrypt(&self, password: &str) -> anyhow::Result { - match self { - Note::Encrypted { id, title, metadata, encrypted_text } => { - let mut eraser = Eraser::new(); - eraser.add_suite::(); - let erased: ErasedPwBox = serde_json::from_str(encrypted_text)?; - - let decrypted_bytes = eraser.restore(&erased)?.open(password)?.to_vec(); - let decrypted_text = String::from_utf8(decrypted_bytes)?; - - Ok(Self::Decrypted { - id: id.clone(), - title: title.clone(), - metadata: metadata.clone(), - text: decrypted_text, - }) - } - _ => Ok(self.clone()), - } - } - - pub fn hide(self) -> Self { - match self { - Note::Decrypted { id, title, metadata, text } => { - Self::Hidden { id, title, metadata, text } - } - _ => unreachable!() - } - } - - pub fn unhide(self) -> Self { - match self { - Note::Hidden { id, title, metadata, text } => { - Self::Decrypted { id, title, metadata, text } - } - _ => unreachable!() - } - } - pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option { let mut value = None; ui.group(|ui| { match self { - Note::Encrypted { id, title, metadata, .. } => { - let result = render_title_and_metadata(ui, title, metadata, false, None); + Note::Encrypted(note) => { + let result = render_title_and_metadata(ui, ¬e.title, ¬e.metadata, false, None); if let Some(action) = result { match action { - NoteRenderAction::Delete => cb(id.to_string()), + NoteRenderAction::Delete => cb(note.id.to_string()), _ => unreachable!() } } if ui.button("decrypt note").clicked() { - value = Some(self.decrypt(password).unwrap()); + value = Some(note.decrypt(password).unwrap().into()); } } - Note::Decrypted { id, title, metadata, text, .. } => { - let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Hide)); + Note::Decrypted(note) => { + let result = render_title_and_metadata(ui, ¬e.title, ¬e.metadata, true, Some(ButtonType::Hide)); if let Some(action) = result { match action { - NoteRenderAction::Delete => cb(id.to_string()), - NoteRenderAction::Hide => value = Some(self.clone().hide()), + NoteRenderAction::Delete => cb(note.id.to_string()), + NoteRenderAction::Hide => value = Some(note.clone().hide().into()), _ => unreachable!() } } - ui.label(text); + ui.label(¬e.text); } - Note::Hidden { id, title, metadata, .. } => { - let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Unhide)); + Note::Hidden(note) => { + let result = render_title_and_metadata(ui, ¬e.0.title, ¬e.0.metadata, true, Some(ButtonType::Unhide)); if let Some(action) = result { match action { - NoteRenderAction::Delete => cb(id.to_string()), - NoteRenderAction::Unhide => value = Some(self.clone().unhide()), + NoteRenderAction::Delete => cb(note.0.id.to_string()), + NoteRenderAction::Unhide => value = Some(note.clone().unhide().into()), _ => unreachable!() } } @@ -142,14 +150,6 @@ impl Note { value } - - pub fn id(&self) -> String { - match self { - Note::Encrypted { id, .. } => id.clone(), - Note::Decrypted { id, .. } => id.clone(), - Note::Hidden { id, .. } => id.clone(), - } - } } enum NoteRenderAction { diff --git a/src/password/mod.rs b/src/password/mod.rs index da8be87..ec4defc 100644 --- a/src/password/mod.rs +++ b/src/password/mod.rs @@ -1,7 +1,7 @@ use anyhow::bail; use argon2::Argon2; use sha2::{Sha256, Digest}; -use crate::notes::Note; +use crate::notes::{EncryptedNote, Note}; use crate::saving::Saving; pub fn generate_new_password(password: &str) -> String { @@ -35,7 +35,7 @@ pub enum PasswordSystem { V1(String), } -pub fn check_password(password: &str, note: &Note) -> anyhow::Result { +pub fn check_password(password: &str, note: &EncryptedNote) -> anyhow::Result { if note.decrypt(password).is_err() { let new_password = generate_new_password(password); if note.decrypt(&new_password).is_err() { @@ -59,7 +59,6 @@ fn hash_password(password: &[u8], output: &mut [u8]) { pub fn migrate_to_password_v1(password: &str, notes: &Vec, saving: &Saving) -> anyhow::Result { let new_password = generate_new_password(password); for note in notes { - let note = note.decrypt(password)?; saving.save_note(note, &new_password)?; } diff --git a/src/saving/mod.rs b/src/saving/mod.rs index 8fbbd26..21fbf10 100644 --- a/src/saving/mod.rs +++ b/src/saving/mod.rs @@ -30,10 +30,15 @@ impl Saving { Ok(notes.into()) } - pub fn save_note(&self, note: Note, password: &str) -> anyhow::Result<()> { - let note = note.encrypt(password)?; + pub fn save_note(&self, note: &Note, password: &str) -> anyhow::Result<()> { + let note = match note { + Note::Encrypted(note) => note.clone(), + Note::Decrypted(note) => note.encrypt(password)?, + Note::Hidden(note) => note.0.encrypt(password)?, + }; + let mut path = self.path.clone().join("_"); - path.set_file_name(format!("{}.json", note.id())); + path.set_file_name(format!("{}.json", note.id)); fs::write(path, serde_json::to_string(¬e)?)?;