use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::time::SystemTime; use chrono::{DateTime, Utc}; use egui::{RichText, Ui}; use pwbox::sodium::Sodium; use pwbox::{ErasedPwBox, Eraser, Suite}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NoteMetadata { pub date: SystemTime, pub arbitrary: Option>, } #[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, } } 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 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, .. } => { render_title_and_metadata(ui, title, metadata, false, (id.clone(), Box::new(cb))); if ui.button("decrypt note").clicked() { value = Some(self.decrypt(password).unwrap()); } } Note::Decrypted { id, title, metadata, text, .. } => { render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb))); ui.label(text); } }; }); value } pub fn id(&self) -> String { match self { Note::Encrypted { id, .. } => id.clone(), Note::Decrypted { id, .. } => id.clone(), } } } fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box)) { ui.horizontal(|ui| { ui.label(RichText::new(title).size(14.0)); let date: DateTime = metadata.date.into(); ui.separator(); ui.label(date.format("%d-%m-%y %H:%M").to_string()); if let Some(arbitrary) = &metadata.arbitrary { for (k, v) in arbitrary.iter() { ui.separator(); ui.label(RichText::new(format!("{}: {}", k, v)).monospace()); } } ui.separator(); if show_delete_button { let (id, cb) = delete_info; if ui.button("delete note").clicked() { cb(id); } } }); ui.separator(); }